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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

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