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");

下面,逐步分析下其实现:

  1. 所有的 log 数据都会发给 DDLog 对象;
  2. DDLog 对象再把 log 数据派发给已添加的各个 Logger 对象;
  3. DDLogger 对象将 log 数据通过其配置的 DDLogFomatter 格式类进行格式处理后输出;
//不同级别的 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];
}

- (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数量:

+ (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));
    });
}
- (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、日志的格式、日志的输出等等,使用起来也简单方便。

上一篇下一篇

猜你喜欢

热点阅读