深入CocoaLumberjack日志系统

引入

在iOS开发中,日志系统是很重要的一个部分,尤其是在修复代码中的bug,通常会用NSLog来将这些信息打印到XCode控制台中显示,但在日志信息较多的时候,会出现一些性能上的问题。因为NSLog在使用的时候占用资源较多,其设计也是基于ASL(Apple System Log)的高层封装,针对error log。所以在对性能要求较高的地方,不能用NSLog进行调试。

作为NSLog的替代,CocoaLumberjack是一款优秀的第三方日志系统。官方的介绍如下:

CocoaLumberjack is a fast & simple, yet powerful & flexible logging framework for Mac and iOS.

It is similar in concept to other popular logging frameworks such as log4j, yet is designed specifically
for Objective-C, and takes advantage of features such as multi-threading, grand central dispatch (if available),
lockless atomic operations, and the dynamic nature of the Objective-C runtime.

支持用CocoaPods和Carthage部署到项目中,当前的系统要求如下:

在设计中,CocoaLumberjack使用了大量的GCD语法,使性能更好;宏定义的使用,让API接口语法更加简便;加上整体架构的设计独特,使这套第三方日志系统更利于根据一些特殊需求来定制功能。

代码分析

使用CocoaLumberjack的时候,会引用其头文件,Oc的项目引用CocoaLumberjack.h,swift项目CocoaLumberjack.swift。

这里,我们从CocoaLumberjack.h展开,文件开头给出了大段的注释信息,详细介绍了关于该日志系统的使用(在该源码的其他头文件中,同样可以看到消息的注释信息,对该接口甚至类的设计进行解说,可见一套优秀的开源项目在细节上是做的多么细致,值得学习!),随后,引入了一系列的头文件:

  1. DDLog是该日志系统中核心的部分,接口调用都通过该类进行调用,在该类中封装了许多的协议和基础类,也算是作为整个日志系统的执行中枢,给整套架构打下基础。

  2. DDLogMacros.hDDAssertMacros.h这两个文件中定义了大量的宏,都是对DDLog的接口进行的封装,封装后,对DDLog的使用变得更加简单。

  3. DDASLLogCapture是用来捕获ASL消息的工具类。

  4. DDTTYLoggerDDASLLoggerDDFileLoggerDDOSLogger是提供具体日志功能的类,分别对应着控制台日志功能、ASL日志功能、文件日志功能和oslog日志功能。如果需要增加新的日志功能,可以模仿这几个类,实现新功能。

DDLog类

是单例,其内部维持了一个dispatch_queue_tdispatch_group_t和信号锁dispatch_semaphore_t(最大为1000个线程同时访问),通过这类GCD属性可以很好地发挥出多线程的优势。此外在该类中还维持着一个loggers数组,数组里面的每个元素为DDLoggerNode类型的元素。DDLogDDLoggerNode来保存被添加的logger(同之前介绍的logger),并将添加logger时的level和logger中维持的线程也一并添加到DDLoggerNode里。这样当DDLog需要log一条message的时候,就会去遍历这个数组,然后根据level在相应的dispatch_queue_t中执行。

DDLog中,定义了创建logger需要的一些接口和基础类,DDLoggerDDLogFormatter是两个协议,DDLogger是创建的logger必须遵循的协议,内部的定义如下:

- (void)logMessage:(DDLogMessage *)logMessage NS_SWIFT_NAME(log(message:));

@property (nonatomic, strong) id <DDLogFormatter> logFormatter;

@optional

- (void)didAddLogger;

- (void)didAddLoggerInQueue:(dispatch_queue_t)queue;

- (void)willRemoveLogger;

- (void)flush;

@property (nonatomic, DISPATCH_QUEUE_REFERENCE_TYPE, readonly) dispatch_queue_t loggerQueue;

@property (nonatomic, readonly) NSString *loggerName;

其中logMessage是用来被调用执行log的接口,在DDLog中被调用;logFormatter是一个遵循DDLogFormatter协议的类,主要用来控制log的格式(控制台输出或者存入文件);接下来的did和will开头的接口为事件响应,可以选择去实现在相应事件发生时候的处理;flush是在一些关系内存和io操作需要实现的类,它可以控制在内存不足的时候将buffer存入磁盘或者相关的数据库;最后loggerQueueloggerName用来获取在logger中维持的线程队列,loggerName是用来创建队列用的名字,在DDAbstractLogger中可以看到logger线程队列是如何维持的。

在创建logger的时候,只需继承于DDAbstractLogger,其内部会维持一个CDG队列,然后遵循DDLogger协议,接口都在协议里面定义好了。在logger里面只需专注于功能的实现,这样可以省去很多接口定义和属性维持方面的事。

同样在DDLog里面会出现很多类似 id<DDLogger> 的属性,通过协议,即使不知道其具体实现,也可以调用接口。通过DDAbstractLogger基类和DDLogger协议,为拓展做了很好的基础。

CocoaLumberjack的架构关系如下图:

对比实验

苹果官方在2016年的WWDC上放出了新的日志系统(The unified logging system),提供了日志信息分类统计和关键信息捕捉等功能,而且在速度上会更快、使用更方便、提供给开发者的可控性也更多。

最后对命令行输出效率进行实验,这里对NSLogprintfos_logwritev在命令行的输出性能进行了对比:

    CFAbsoluteTime startNSLog = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 10000; i++) {
        NSLog(@"%d", i);
    }
    CFAbsoluteTime endNSLog = CFAbsoluteTimeGetCurrent();
    
    CFAbsoluteTime startPrintf = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 10000; i++) {
        printf("%d\n", i);
    }
    CFAbsoluteTime endPrintf = CFAbsoluteTimeGetCurrent();
    
    CFAbsoluteTime startOsLog = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 10000; i++) {
        os_log(OS_LOG_DEFAULT, "%d\n", i);
    }
    CFAbsoluteTime endOsLog = CFAbsoluteTimeGetCurrent();
    
    CFAbsoluteTime startWritev = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 10000; i++) {
        struct iovec v[1];
        v[0].iov_base = "a\n";
        v[0].iov_len = 2;
        writev(STDERR_FILENO, v, 1);
    }
    CFAbsoluteTime endWritev = CFAbsoluteTimeGetCurrent();
    
    NSLog(@"NSLog time: %lf, printf time: %lf", endNSLog - startNSLog, endPrintf - startPrintf);
    NSLog(@"OsLog time: %lf, writev time: %lf", endOsLog - startOsLog, endWritev - startWritev);

Mac电脑上的运行时间:


虚拟机运行时间:


真机运行时间:


通过对比可知,采用writev和printf这种底层的命令行输出效率最高。

总结:

  1. CocoaLumberjack架构设计精妙,接口易于拓展定制功能。
  2. 苹果提供了新的日志系统API,但在命令行的输出上底层接口的效率更高。

参考:
NSLog效率低下的原因及尝试lldb断点打印Log
WWDC2016 session721
Logging

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

推荐阅读更多精彩内容