iOS日志框架CocoaLumberjack分析

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,421评论 19 139
  • GCD调度队列是执行任务的强大工具。调度队列允许您相对于调度者异步或者同步的执行任意代码块。您能够使用调度队列来执...
    坤坤同学阅读 11,647评论 1 3
  • 导语: DDLog,即CocoaLumberjack是iOS开发用的最多的日志框架,出自大神Robbie Hans...
    杭研融合通信iOS阅读 6,495评论 0 8
  • ~感恩天地滋养万物~ ~感恩祖先慈悲智慧~ ~感恩国家培养护佑~ ~感恩父...
    和谐2号阅读 1,118评论 0 0
  • 男女之间有纯友谊么?这是个千古问题,对于这个问题,国外的一个小伙子在图书馆附近做了个小范围的调查,调查的结果很有趣...
    DD66阅读 3,315评论 0 1