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 )logger withLevel:(DDLogLevel)level {
    if (!logger) {
        return;
    }
    
    dispatch_async(_loggingQueue, ^{ @autoreleasepool {
        [self lt_addLogger:logger level:level];
    } });
}

精简代码

- (void)lt_addLogger:(id )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);

这个宏最后会调用

CocoaLumberjack 源码分析_第1张图片
调用堆栈.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 
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];
        } });

你可能感兴趣的:(CocoaLumberjack 源码分析)