简单说两句
objective-c 强大的runtime机制,让开发者不用掌握源码都可以拦截原始方法做一些自己想做的事。
Method Swizzling 不说了。
鉴于开发者常用NSLog、fprintf等方法做日志输出,本文记录如何拦截交换NSLog、fprintf等C方法,并持久化到沙盒目录。
感谢 fishhook 和 CocoaLumberjack。
写一个APP内拦截NSLog的宏
这个宏可以将历史源码中的原始NSLog方法做拦截,转移到自定义的方法中,本文基于CocoaLumberjack
做日志输出。
#ifdef DEBUG
static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
#else
static const DDLogLevel ddLogLevel = DDLogLevelVerbose ^ DDLogFlagDebug;
#endif
#define NSLog(frmt, ...) do {DDLogDebug(frmt, ##__VA_ARGS__); } while(0)
这样,APP内大量历史原因沉淀下来的原始NSLog方法都可以路由到DDLog里统一维护了。 建议基于DDLog做一个针对Log的适配封装,本文仅做简单演示,先忽略这层封装。
这就够了吗
这还是不够的,由于项目中引入了一些framework,有些framework内部的日志不方便在APP内输出、查看。
搞起来
写一个通用的拦截交换方法
/**
拦截、监听C方法
@param orignFunctionName 原始方法名,char字符串
@param newFunction 本地实现的新的函数
@param cachedOldFunction 用来保存原始函数的函数
*/
- (void)startCFunctionMonitor:(const char *__nonnull)orignFunctionName withNewFunction:(void* __nonnull)newFunction andCachedOldFunction:(void** __nullable)cachedOldFunction{
struct rebinding logBind;
//要拦截的原始函数名称
logBind.name = orignFunctionName;
//本地实现的新的函数
logBind.replacement = newFunction;
//保存原始函数
logBind.replaced = cachedOldFunction;
struct rebinding rebs[] = {logBind};
rebind_symbols(rebs, 1);
}
写一个拦截实现方法
//用来保存原始的NSLog函数的地址
static void(*qg_old_nslog)(NSString *format, ...);
void QGNSLog(NSString *format, ...){
va_list vl;
va_start(vl, format);
NSString* str = [[NSString alloc] initWithFormat:format arguments:vl];
va_end(vl);
NSLog(str);//这个NSLog是上面写的宏,已经不是原始的NSLog了
// qg_old_nslog(@"%@",str);//如果需要,可以让原始方法做输出
}
void qg_fprintf(FILE * file, const char * format, ...){
va_list vl;
va_start(vl, format);
NSString* str = [[NSString alloc] initWithFormat:[NSString stringWithFormat:@"%s",format] arguments:vl];
va_end(vl);
NSLog(str);//这个NSLog是上面写的宏,已经不是原始的NSLog了
}
APP内的配置
基于以上步骤,APP内、无源码的framework等场景都可以拦截到常用的日志输出方法了。
APP内初始化的时候做个简单的配置
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self configLog];
....
return YES;
}
// 配置日志
- (void)configLog {
//拦截漏网的NSLog方法
[self startCFunctionMonitor:"NSLog" withNewFunction:QGNSLog andCachedOldFunction:(void**)&qg_old_nslog];
//拦截fprintf方法
[self startCFunctionMonitor:"fprintf" withNewFunction:qg_fprintf andCachedOldFunction:NULL];
//输出到控制台
[DDLog addLogger:[DDASLLogger sharedInstance]]; // ASL = Apple System Logs
/* 将日志存储到本地 */
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *baseDir = paths.firstObject;
NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"AppLogs"];
DDLogFileManagerDefault *logFileManager = [[DDLogFileManagerDefault alloc] initWithLogsDirectory:logsDirectory];
DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:logFileManager];
fileLogger.rollingFrequency = 60 * 60 * 24;
fileLogger.logFileManager.maximumNumberOfLogFiles = 20;
fileLogger.maximumFileSize = 1024 * 1024 * 2;
[DDLog addLogger:fileLogger];
}
后续
建议集成 DoraemonKit ,很方便开发者做调试使用。 基于 DoraemonKit 提供的能力,可以快速查看沙盒目录里自己存储的日志了,可以预览、分享(导出)。
自由发挥。