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);
这个宏最后会调用
- (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];
具体看下 FileLogger
的logMessage
实现
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];
} });