美文网首页
iOS日志框架CocoaLumberjack分析

iOS日志框架CocoaLumberjack分析

作者: 一剑书生 | 来源:发表于2017-07-02 15:11 被阅读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 信息输出到前面添加的渠道。

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

    1. 所有的 log 数据都会发给 DDLog 对象;
    2. DDLog 对象再把 log 数据派发给已添加的各个 Logger 对象;
    3. 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_waitdispatch_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_specificdispatch_queue_set_specific 方法,作用类似于objc_setAssociatedObjectobjc_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、日志的格式、日志的输出等等,使用起来也简单方便。

    相关文章

      网友评论

          本文标题:iOS日志框架CocoaLumberjack分析

          本文链接:https://www.haomeiwen.com/subject/fsnncxtx.html