iOS日志框架CocoaLumberjack分析
2017-07-02 本文已影响666人
一剑书生
CocoaLumberjack 是 支持 iOS 和 Mac 平台的日志框架,使用简单,功能强大且不失灵活,它的主要功能就是:支持不同 Level 的 Log 信息输出到各个渠道,包括苹果日志系统、Xcode 控制台、本地文件或者数据库。
使用方法如下:
//1,设置 Log 输出渠道
[DDLog addLogger:[DDTTYLogger sharedInstance]]; // TTY = Xcode console
[DDLog addLogger:[DDASLLogger sharedInstance]]; // ASL = Apple System Logs
DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
[DDLog addLogger:fileLogger];
//2,打印 log 信息
DDLogVerbose(@"Verbose"); // Log 信息
DDLogDebug(@"Debug");
DDLogInfo(@"Info");
DDLogWarn(@"Warn");
DDLogError(@"Error");
- 首先,通过 DDLog 的
addLogger:(id <DDLogger>)logger
方法添加 Log 数据输出的渠道,上面代码是添加了DDTTYLogger
(Xcode 控制台)、DDASLLogger
(苹果日志系统)、DDFileLogger
(本地文件)三种渠道; - 然后,使用区分不同 Level 的宏定义方法,即可把 Log 信息输出到前面添加的渠道。
下面,逐步分析下其实现:
- 所有的 log 数据都会发给 DDLog 对象;
- DDLog 对象再把 log 数据派发给已添加的各个 Logger 对象;
- DDLogger 对象将 log 数据通过其配置的
DDLogFomatter
格式类进行格式处理后输出;
- 第一步,以调用
DDLogVerbose
log 信息为例,DDLogVerbose
宏定义实则为了方便调用 log 信息:
//不同级别的 Log 宏定义
#define DDLogVerbose(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagVerbose, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
...
#define LOG_MAYBE(async, lvl, flg, ctx, tag, fnct, frmt, ...) \
do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, tag, fnct, frmt, ##__VA_ARGS__); } while(0)
#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \
[DDLog log : isAsynchronous \
level : lvl \
flag : flg \
context : ctx \
file : __FILE__ \
function : fnct \
line : __LINE__ \
tag : atag \
format : (frmt), ## __VA_ARGS__]
从上面看到 LOG_MAYBE
宏定义通过 if(lvl & flg)
判断处理了不同级别的Log信息是否输出的逻辑,然后调用 LOG_MACRO
,最终调用的是DDLog
的方法:
- (void)log:(BOOL)asynchronous
message:(NSString *)message
level:(DDLogLevel)level
flag:(DDLogFlag)flag
context:(NSInteger)context
file:(const char *)file
function:(const char *)function
line:(NSUInteger)line
tag:(id)tag {
DDLogMessage *logMessage = [[DDLogMessage alloc] initWithMessage:message
level:level
flag:flag
context:context
file:[NSString stringWithFormat:@"%s", file]
function:[NSString stringWithFormat:@"%s", function]
line:line
tag:tag
options:(DDLogMessageOptions)0
timestamp:nil];
[self queueLogMessage:logMessage asynchronously:asynchronous];
}
- 第二步把 log 数据派发给已添加的 Logger,
- (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag {
dispatch_semaphore_wait(_queueSemaphore, DISPATCH_TIME_FOREVER); //信号量减一
......
dispatch_block_t logBlock = ^{
@autoreleasepool {
[self lt_log:logMessage];
}
};
if (asyncFlag) {
dispatch_async(_loggingQueue, logBlock);
} else {
dispatch_sync(_loggingQueue, logBlock);
}
}
- (void)lt_log:(DDLogMessage *)logMessage {
// Execute the given log message on each of our loggers.
NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey),
@"This method should only be run on the logging thread/queue");
if (_numProcessors > 1) {
for (DDLoggerNode *loggerNode in self._loggers) {
// skip the loggers that shouldn't write this message based on the log level
if (!(logMessage->_flag & loggerNode->_level)) {
continue;
}
dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool {
[loggerNode->_logger logMessage:logMessage];
} });
}
dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER);
} else {
// Execute each logger serialy, each within its own queue.
//针对于单核处理器做的优化处理
....
}
dispatch_semaphore_signal(_queueSemaphore);//信号量加一
}
从上面代码看到,所有的 log 消息都会添加到 loggingQueue 串行队列中,确保先进先出,而且使用了信号量来限制添加到队列中的log数量:
-
dispatch_semaphore_wait
与dispatch_semaphore_signal
的使用比较简单,dispatch_semaphore_wait
信号量减一,dispatch_semaphore_signal
信号量加一, 当信号量为0时线程就会进入等待状态,直到信号量大于0时才会执行下去;创建信号量的时候可指定信号量个数:
+ (void)initialize {
static dispatch_once_t DDLogOnceToken;
dispatch_once(&DDLogOnceToken, ^{
NSLogDebug(@"DDLog: Using grand central dispatch");
_loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL);//串行队列
_loggingGroup = dispatch_group_create();
void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null
dispatch_queue_set_specific(_loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL); //添加标志
_queueSemaphore = dispatch_semaphore_create(DDLOG_MAX_QUEUE_SIZE);//通过信号量设置队列最大数量
......
_numProcessors = MAX([NSProcessInfo processInfo].processorCount, (NSUInteger) 1);
NSLogDebug(@"DDLog: numProcessors = %@", @(_numProcessors));
});
}
-
dispatch_get_specific
与dispatch_queue_set_specific
方法,作用类似于objc_setAssociatedObject
跟objc_getAssociatedObject
,在线程队列中添加标志,运行的时候取出标志便可判断某个方法是否运行在指定的队列中。从上面代码可看出,在 loggingQueue 队列创建的时候就设置了标识GlobalLoggingQueueIdentityKey
, 到时候取出当前线程的标志进行判断,来确保lt_log:(DDLogMessage *)logMessage
方法在loggingQueue
队列中。 - 使用
dispatch_group_wait
方法,使得多个并发 block 全部执行完成后程序才会执行下去。这里,NSLog 对象会将 log 信息派发给多个渠道 (NSLogger)并发执行记录 log 信息,执行完之后便会将信号量加一。
- 第三步,不同的 NSLogger 会在自己的线程队列中记录 log 信息,下面来看
DDFileLogger
的处理:
- (void)logMessage:(DDLogMessage *)logMessage {
NSString *message = logMessage->_message;
BOOL isFormatted = NO;
if (_logFormatter) {//格式化log数据
message = [_logFormatter formatLogMessage:logMessage];
isFormatted = message != logMessage->_message;
}
if (message) {
if ((!isFormatted || _automaticallyAppendNewlineForCustomFormatters) &&
(![message hasSuffix:@"\n"])) {//添加换行符
message = [message stringByAppendingString:@"\n"];
}
NSData *logData = [message dataUsingEncoding:NSUTF8StringEncoding];
@try {
[self willLogMessage];
[[self currentLogFileHandle] writeData:logData]; //写入文件中
[self didLogMessage];
} @catch (NSException *exception) {
exception_count++;
.....
}
}
代码比较简单明了, 就是把log信息格式化后写到文件中,写入文件后会检查当前文件大小,文件超过最大限制后就会去回滚归档日志文件。
最后,总结一下,CocoaLumberjack 日志框架的拓展灵活性相当不错,可自定义日志 level、日志的格式、日志的输出等等,使用起来也简单方便。