使用CocoaLumberjack分模块保存日志文件

662 阅读2分钟

使用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/…