CocoaLumberjack 源码分析

CocoaLumberjack GitHub 地址 当期版本[3.4.2]


DDLog 是开发中常用的日志基础库,以前只是简单的使用和NSLog 使用无区别,最近想做一个日志基础模块,所以研究了下 DDLog 的源码。
初始化方法

+ (void)initialize {
    static dispatch_once_t DDLogOnceToken;
    
    dispatch_once(&DDLogOnceToken, ^{
       
               
       //为  _loggingQueue 设置一个标识符
        _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);

        //#define DDLOG_MAX_QUEUE_SIZE 1000
        // 信号量 保证执行的最大任务数
        _queueSemaphore = dispatch_semaphore_create(DDLOG_MAX_QUEUE_SIZE);
        
        //进程数
        _numProcessors = MAX([NSProcessInfo processInfo].processorCount, (NSUInteger) 1);
        
        NSLogDebug(@"DDLog: numProcessors = %@", @(_numProcessors));
    });
}

先从DDLog 用法说起

if(DEBUG){
        // 在调试模式下 输出到控制台,发布模式不输出控制台
        [DDLog addLogger:[DDTTYLogger sharedInstance]];
    }

最先使用时是以 addLogger 方法开始。

- (void)addLogger:(id <DDLogger>)logger withLevel:(DDLogLevel)level {
    if (!logger) {
        return;
    }
    
    dispatch_async(_loggingQueue, ^{ @autoreleasepool {
        [self lt_addLogger:logger level:level];
    } });
}

精简代码

- (void)lt_addLogger:(id <DDLogger>)logger level:(DDLogLevel)level {
    
    //判断logger 是否已经添加过
    //判断当期 quque 是不是 GlobalLogQueue
    // loggerQueue 懒加载 为每一个 logger 分配一个 quque
 DDLoggerNode *loggerNode = [DDLoggerNode nodeWithLogger:logger loggerQueue:loggerQueue level:level];
    //加入 self._loggers 中
    
    //切片化设计,通知外部 添加成功
    if ([logger respondsToSelector:@selector(didAddLoggerInQueue:)]) {
        dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool {
            [logger didAddLoggerInQueue:loggerNode->_loggerQueue];
        } });
    } else if ([logger respondsToSelector:@selector(didAddLogger)]) {
        dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool {
            [logger didAddLogger];
        } });
    }
}

使用记录日志

  DDLogInfo(@"%@",string);

这个宏最后会调用

调用堆栈.jpg
- (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag {

    dispatch_block_t logBlock = ^{
        dispatch_semaphore_wait(_queueSemaphore, DISPATCH_TIME_FOREVER);
        @autoreleasepool {
            [self lt_log:logMessage];
        }
    };

    if (asyncFlag) {
        dispatch_async(_loggingQueue, logBlock);
    } else {
        dispatch_sync(_loggingQueue, logBlock);
    }
}
- (void)lt_log:(DDLogMessage *)logMessage {
    //处理器为多核CPU 时使用多线程,单核时同步执行
    if (_numProcessors > 1) {
      //多核CPU

        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;
            }
            //log 任务放入 group 中
            dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool {
                [loggerNode->_logger logMessage:logMessage];
            } });
        }
          //通过group 保证每一条log信息在各个 logger 处理完后再往下执行,
            //保证了日志信息的同步
        dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER);
    } else {
       
        
        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;
            }
            //单核CPU 就一条任务一条任务执行
            dispatch_sync(loggerNode->_loggerQueue, ^{ @autoreleasepool {
                [loggerNode->_logger logMessage:logMessage];
            } });
        }
    }
    //执行完后改变信号量
    dispatch_semaphore_signal(_queueSemaphore);
}

到此为止 DDLog.m 中的代码已经完毕,调用各个 logger 中的
[logger logMessage:logMessage];发放进行对应日志的处理
下面主要对 DDFileLogger 进行分析。


DDFileLogger文件中主要有以下几个类

DDLogFileManagerDefault
DDLogFileFormatterDefault
DDFileLogger : DDAbstractLogger <DDLogger>
DDLogFileInfo

最简单的DDLogFileInfo就是对文件对象的一个包装,记录一些文件信息。
下面三个类通过代码串起来解释
上面调用了[logger logMessage:logMessage];具体看下 FileLoggerlogMessage实现

static int exception_count = 0;
- (void)logMessage:(DDLogMessage *)logMessage {
    NSString *message = logMessage->_message;
    BOOL isFormatted = NO;
    
    //对数据进行格式化  
    // DDLogFileFormatterDefault 就是 实现了协议中的
    //  formatLogMessage 方法 输出特定的格式
    if (_logFormatter) {
        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) {
            
        }
    }
}

第一步
先看[[self currentLogFileHandle] writeData:logData];方法

- (NSFileHandle *)currentLogFileHandle {

    if (_currentLogFileHandle == nil) {
        //创建文件
        NSString *logFilePath = [[self currentLogFileInfo] filePath];

        _currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
        [_currentLogFileHandle seekToEndOfFile];

        if (_currentLogFileHandle) {
            //step 1.1
            [self scheduleTimerToRollLogFileDueToAge];
            
             //下面代码是检测 文件名变更进行相应的处理 调用 
            // [self rollLogFileNow];
            _currentLogFileVnode = dispatch_source_create(
                    DISPATCH_SOURCE_TYPE_VNODE,
                    [_currentLogFileHandle fileDescriptor],
                    DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME,
                    self.loggerQueue
                    );

            dispatch_source_set_event_handler(_currentLogFileVnode, ^{ @autoreleasepool {
                                      //记录一下 这里调用了   [self rollLogFileNow];
                                       [self rollLogFileNow];
                                                                      } });

            dispatch_resume(_currentLogFileVnode);
        }
    }

    return _currentLogFileHandle;
}

step1.1

- (void)scheduleTimerToRollLogFileDueToAge {
   //设置定时器去查看是否需要将当前日志 需要调用 
   //  [self rollLogFileNow]; 存档日志

    dispatch_source_set_event_handler(_rollingTimer, ^{ @autoreleasepool {
                                                           [self maybeRollLogFileDueToAge];
                                                       } });

    
}

第二步

- (void)didLogMessage {
    [self maybeRollLogFileDueToSize];
}

- (void)maybeRollLogFileDueToSize {
 
    if (_maximumFileSize > 0) {
        unsigned long long fileSize = [_currentLogFileHandle offsetInFile];

        if (fileSize >= _maximumFileSize) {
            //存档log
            [self rollLogFileNow];
        }
    }
}

- (void)rollLogFileNow {
   
    if (_currentLogFileHandle == nil) {
        return;
    }

    [_currentLogFileHandle synchronizeFile];
    [_currentLogFileHandle closeFile];
    _currentLogFileHandle = nil;
//标记文件 压缩
    _currentLogFileInfo.isArchived = YES;

// 调用 logFileManager 的 didRollAndArchiveLogFile 方法
// FileManger 处理压缩问题
    if ([logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)]) {
        [logFileManager didRollAndArchiveLogFile:(_currentLogFileInfo.filePath)];
    }
//所有信息 置为空
    _currentLogFileInfo = nil;

    if (_currentLogFileVnode) {
        dispatch_source_cancel(_currentLogFileVnode);
        _currentLogFileVnode = NULL;
    }

    if (_rollingTimer) {
        dispatch_source_cancel(_rollingTimer);
        _rollingTimer = NULL;
    }
}

到此FileLog相关核心代码已分析完毕
接下来看 FileManager, 上面有调用 [logFileManager didRollAndArchiveLogFile:(_currentLogFileInfo.filePath)];
fileManager 可以在 didRollAndArchiveLogFile 方法中进行文件的压缩。


总结
DDLog 关键点

//log 任务放入 group 中
            dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool {
                [loggerNode->_logger logMessage:logMessage];
            } });
        }
          //通过group 保证每一条log信息在各个 logger 处理完后再往下执行,
            //保证了日志信息的同步
        dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER);

小知识点

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

推荐阅读更多精彩内容