使用CocoaLumberjack分模块保存日志文件
项目中使用CocoaLumberjack来做日志的保存,现在需要对不同模块做不同的文件保存,用于之后的日志分析和上传服务器来处理用户售后问题的调查。
改造之前直接使用DDLog来输出日志
#import <CocoaLumberjack/CocoaLumberjack.h>
static const DDLogLevel ddLogLevel = DDLogLevelAll;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
// 1.发送到 Xcode 控制台
[DDLog addLogger:[DDTTYLogger sharedInstance]];
DDLogDebug(@"This is debug level log");
DDLogWarn(@"This is warn level log");
DDLogInfo(@"This is info level log");
DDLogError(@"This is error level log");
}
参考DDLogDebug等 宏的定义,如下
DDLegacyMacros.h
#define DDLogError(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_ERROR, LOG_LEVEL_DEF, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__)
#define DDLogWarn(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_WARN, LOG_LEVEL_DEF, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__)
#define DDLogInfo(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_INFO, LOG_LEVEL_DEF, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__)
#define DDLogDebug(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_DEBUG, LOG_LEVEL_DEF, LOG_FLAG_DEBUG, 0, frmt, ##__VA_ARGS__)
#define DDLogVerbose(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_VERBOSE, LOG_LEVEL_DEF, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__)
定义自己业务类的日志输出宏定义: PKMacro.h
static const DDLogLevel ddLogLevel = DDLogLevelAll;
#define PK_LOG_SOCKET_FLAG ( 1 << 10)
#define PK_LOG_BIZ_FLAG (1 << 11)
#define PK_LOG_SOCKET_LEVEL (PK_LOG_SOCKET_FLAG)
#define PK_LOG_BIZ_LEVEL (PK_LOG_BIZ_FLAG)
#define PK_SOCKET_LOGDEBUG(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug, PK_LOG_SOCKET_LEVEL, nil, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
#define PK_SOCKET_LOGINFO(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, PK_LOG_SOCKET_LEVEL, nil, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
#define PK_SOCKET_LOGWARN(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagWarning, PK_LOG_SOCKET_LEVEL, nil, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
#define PK_SOCKET_LOGERROR(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagError, PK_LOG_SOCKET_LEVEL, nil, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
#define PK_BIZ_LOGDEBUG(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug, PK_LOG_BIZ_LEVEL, nil, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
#define PK_BIZ_LOGINFO(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, PK_LOG_BIZ_LEVEL, nil, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
#define PK_BIZ_LOGWARN(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagWarning, PK_LOG_BIZ_LEVEL, nil, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
#define PK_BIZ_LOGERROR(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagError, PK_LOG_BIZ_LEVEL, nil, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
自定义日志文件保存类
PKSocketFileManagerDefault.h
#import <CocoaLumberjack/CocoaLumberjack.h>
NS_ASSUME_NONNULL_BEGIN
@interface PKSocketFileManagerDefault : DDLogFileManagerDefault
- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory fileName:(NSString *)name;
@end
NS_ASSUME_NONNULL_END
PKSocketFileManagerDefault.m
@interface PKSocketFileManagerDefault ()
@property (nonatomic, copy) NSString *fileName;
@end
@implementation PKSocketFileManagerDefault
- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory
fileName:(NSString *)name
{
self = [super initWithLogsDirectory:logsDirectory];
if (self) {
self.fileName = name;
}
return self;
}
#pragma mark - Override methods
- (NSString *)newLogFileName
{
NSDateFormatter *dateFormatter = [self logFileDateFormatter];
NSString *formattedDate = [dateFormatter stringFromDate:[NSDate date]];
return [NSString stringWithFormat:@"%@-%@.log", self.fileName, formattedDate];
}
- (BOOL)isLogFile:(NSString *)fileName
{
return [fileName isEqualToString:self.fileName];
}
- (NSDateFormatter *)logFileDateFormatter {
//获取当前线程的字典
NSMutableDictionary *dictionary = [[NSThread currentThread]
threadDictionary];
//设置日期格式
NSString *dateFormat = @"yyyy'-'MM'-'dd'";
NSString *key = [NSString stringWithFormat:@"logFileDateFormatter.%@", dateFormat];
NSDateFormatter *dateFormatter = dictionary[key];
if (dateFormatter == nil) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"zh_CN"]];
[dateFormatter setDateFormat:dateFormat];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
dictionary[key] = dateFormatter;
}
return dateFormatter;
}
@end
日志分文件的原理是通过自定义LogLevel然后结合白名单的设置来达到日志分模块的效果,自定义白名单过滤器
PKContextWhitelistFilterLogFormatter.h
#import <CocoaLumberjack/CocoaLumberjack.h>
NS_ASSUME_NONNULL_BEGIN
@interface PKContextWhitelistFilterLogFormatter : DDContextWhitelistFilterLogFormatter
@end
NS_ASSUME_NONNULL_END
PKContextWhitelistFilterLogFormatter.m
#import "PKContextWhitelistFilterLogFormatter.h"
@implementation PKContextWhitelistFilterLogFormatter
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage
{
if ([self isOnWhitelist:logMessage->_context]) { // 通过此判断来完成日志的分模块操作
NSString *logLevel;
switch (logMessage->_flag) {
case DDLogFlagError : logLevel = @"ERROR"; break;
case DDLogFlagWarning : logLevel = @"WARN"; break;
case DDLogFlagInfo : logLevel = @"INFO"; break;
case DDLogFlagDebug : logLevel = @"DEBUG"; break;
default : logLevel = @"VIEW"; break;
}
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss SSS"];
[formatter setLocale:[NSLocale currentLocale]];
NSString *timestap = [formatter stringFromDate:logMessage->_timestamp];
return [NSString stringWithFormat:@"%@ | %@ | %@", timestap, logLevel, logMessage->_message];
} else {
return nil;
}
}
@end
完成上述的自定义后,最后来添加到DDLog中
PKLogManager.h
#import <Foundation/Foundation.h>
#import <CocoaLumberjack/CocoaLumberjack.h>
NS_ASSUME_NONNULL_BEGIN
@interface PKLogManager : NSObject
+(id<DDLogger>)createModuleFileManager:(NSInteger) logLevel forFileName:(NSString *) fileName;
@end
NS_ASSUME_NONNULL_END
PKLogManager.m
#import "PKLogManager.h"
#import "PKSocketFileManagerDefault.h"
#import "PKContextWhitelistFilterLogFormatter.h"
#import "PKMacro.h"
@implementation PKLogManager
+(id<DDLogger>)createModuleFileManager:(NSInteger) logLevel forFileName:(NSString *) fileName{
NSString *logsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Logs"];
DDLogInfo(@"Log Path: %@", logsDirectory);
PKSocketFileManagerDefault *bizFileManager = [[PKSocketFileManagerDefault alloc] initWithLogsDirectory:logsDirectory fileName:fileName];
DDFileLogger *bizLogger = [[DDFileLogger alloc] initWithLogFileManager:bizFileManager];
bizLogger.maximumFileSize = 1024 * 1024 * 10; //单文件最大10M
bizLogger.rollingFrequency = 7 * 60 * 60 * 24; //保存7天
bizLogger.logFileManager.maximumNumberOfLogFiles = 7;
PKContextWhitelistFilterLogFormatter *bizFormatter = [[PKContextWhitelistFilterLogFormatter alloc] init];
[bizFormatter addToWhitelist:logLevel];
[bizLogger setLogFormatter:bizFormatter];
[DDLog addLogger:bizLogger withLevel:DDLogLevelAll];
return bizLogger;
}
@end
接下来就可以在项目中使用了:
[PKLogManager createModuleFileManager:PK_LOG_SOCKET_FLAG forFileName:@"ModuleA"];
[PKLogManager createModuleFileManager:PK_LOG_BIZ_FLAG forFileName:@"ModuleB"];
PK_SOCKET_LOGDEBUG(@"Module A Debug log");
PK_SOCKET_LOGWARN(@"Module A Warn log");
PK_SOCKET_LOGINFO(@"Module A Info log");
PK_BIZ_LOGDEBUG(@"Module B Debug Log");
PK_BIZ_LOGWARN(@"Module B Debug Log");
PK_BIZ_LOGINFO(@"Module B Info log");
这样Log就会在沙盒目录下分文件保存,需要上传日志时,到指定目录提取日志就可以嘞。
详细Demo工程请参见: github.com/keepkoding/…