Swift集成CocoaLumberJack日志库(一)

集成背景

Swift自带的print打印日志方式确实是内容变简单了,没有了时间显示在调试某些功能或者bug的时候确实是个很头疼的事情。其次简单的print达不到我们目前的需求,我们需要APP有收集日志和保存到本地并能够分享出来的功能。然后基于CocoaLumberJack日志库的强大选择了此库集成日志收集功能。

基本使用

使用cocoapod安装库:pod 'CocoaLumberjack' .安装好之后,打开项目还不能直接使用DDLog的功能,首先因为日志库对于的各种打印使用Define的方式,swift直接使用不是行的(后面发现是可以使用swift版本的,会在我的另一篇文章写出来)。于是我在项目中新建了一个OC的管理类DDLogWrapper去实现DDLog的打印功能。在swift首次新建的OC类会提醒你创建一个桥接文件点确定就行了,如果是已经有的就不需要创建了,创建好类之后放到桥接文件里

#ifndef DDLogDemo-Bridging-Header.h
#define DDLogDemo-Bridging-Header.h

#import "DDLogWrapper.h"

#endif

在类里面声明好接口

#import 
#import 

NS_ASSUME_NONNULL_BEGIN

@interface DDLogWrapper : NSObject
+ (void)logError:(NSString *)message;
+ (void)logWarn:(NSString *)message;
+ (void)logInfo:(NSString *)message;
+ (void)logDebug:(NSString *)message;
+ (void)logVerbose:(NSString *)message;
@end

NS_ASSUME_NONNULL_END

然后在实现里面调用DDLog的各个等级日志打印功能

#import "DDLogWrapper.h"
// 定义Log等级
static const DDLogLevel ddLogLevel = DDLogLevelDebug;

@implementation DDLogWrapper

+(void)logError:(NSString *)message{
    DDLogError(@"%@",message);
}
+(void)logWarn:(NSString *)message{
    DDLogWarn(@"%@",message);
}
+(void)logInfo:(NSString *)message{
    DDLogInfo(@"%@",message);
}
+(void)logDebug:(NSString *)message{
    DDLogDebug(@"%@",message);
}
+(void)logVerbose:(NSString *)message{
    DDLogVerbose(@"%@",message);
}

@end

然后我们来到Appdelegate里面将其初始化就可以使用了

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 添加logger  下面会说明这个DDOSLogger
        DDLog.add(DDOSLogger.sharedInstance)
        
        DDLogWrapper.logError("你好")
        DDLogWrapper.logWarn("我不好")
        DDLogWrapper.logInfo("为什么不好")
        DDLogWrapper.logDebug("就是不太好")
        DDLogWrapper.logVerbose("你需要治治")

        return true
    }
控制台打印结果

现在打印是有日志时间了,但这里会有人会疑惑了,代码里不是打印了5个吗?为什么控制台只输出了4句,最后一句没有打印?其实我们在创建DDLogWrapper的时候定义了一个log的等级,就是这句:

// 定义log等级
static const DDLogLevel ddLogLevel = DDLogLevelDebug;

CocoaLumberjack提供了不同等级的Log输出,一共有5种等级,以下为5种等级的宏定义。

DDLogError(frmt, ...)      //错误      当ddLogLevel = DDLogLevelError及以上等级时会输出
DDLogWarn(frmt, ...)       //警告      当ddLogLevel = DDLogLevelWarning及以上等级时会输出
DDLogInfo(frmt, ...)       //信息      当ddLogLevel = DDLogLevelInfo及以上等级时会输出
DDLogDebug(frmt, ...)      //调试      当ddLogLevel = DDLogLevelDebug及以上等级时会输出
DDLogVerbose(frmt, ...)    //详细      当ddLogLevel = DDLogLevelVerbose及以上等级时会输出

输出的等级由我们在DDLogWrapper定义的ddLogLevel决定。比如我们上面定义了ddLogLevel = DDLogLevelDebug,那么debug及以下等级的Log都能够输出,而使用DDLogVerbose的Log就不会输出。

logger记录器

在上面的demo里我们在项目入口初始化了一个DDOSLogger

// 添加logger  下面会说明这个DDOSLogger
 DDLog.add(DDOSLogger.sharedInstance)

CocoaLumberjack内置了以下几种Logger。

DDFileLogger:是将log写入到文件中。这也是本篇我们着重要讲的
DDOSLogger:在iOS10开始使用,在将Log输出到 控制台.app 和 Xcode控制台。
DDASLLogger:将日志写入到控制台.app中。在iOS10开始过时
DDTTYLogger:将日志写入到Xcode控制台。

所以,想要替换Swift的Print,官方推荐的做法是:
iOS10及以上系统版本,使用DDOSLogger
iOS10以下版本,使用DDASLLogger+DDTTYLogger

写入文件

之前介绍了DDFileLogger可以将日志写入到文件。添加DDFileLogger就可以将日志记录到文件里面了,跟添加DDOSLogger一样。

let fileLogger = DDFileLogger.init()
DDLog.add(fileLogger)

默认的log日志在沙盒的Library/Caches/Logs目录中,是一个以.log为后缀的文本文件,如下图所示。

生成log文件路径

还可以给DDFileLogger添加各种属性,下面为属性设置都有解释

+ (void)addFileLogger{
    DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
    //重用log文件,不要每次启动都创建新的log文件(默认值是NO)
    fileLogger.doNotReuseLogFiles = NO;
    //log文件在24小时内有效,超过时间创建新log文件(默认值是24小时)
    fileLogger.rollingFrequency = 60*60*24;
    //禁用文件大小滚动
    fileLogger.maximumFileSize = 0;
    //最多保存7个log文件
    fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
    //log文件夹最多保存20M
    fileLogger.logFileManager.logFilesDiskQuota = 1024*1024*20;
    [DDLog addLogger:fileLogger];
}

我这里将DDFileLoggerDDOSLoggerDDLogWrapper里开了一个添加的方法在Appdelegate调用,如下

DDLogWrapper.addOSLogger()
DDLogWrapper.addFileLogger()

重点解释下rollingFrequencymaximumFileSize这两个参数在官方的API里也有举例说明

*For example:
*The rollingFrequency is 24 hours,
*but the log file surpasses the maximumFileSize after only 20 hours.
*The log file will be rolled at that 20 hour mark.
*A new log file will be created, and the 24 hour timer will be restarted.
*You may optionally disable rolling due to filesize by setting maximumFileSize to zero.
*If you do so, rolling is based solely on rollingFrequency.
*You may optionally disable rolling due to time by setting rollingFrequency to zero (or any non-positive number).
*If you do so, rolling is based solely on maximumFileSize.
*If you disable both maximumFileSize and rollingFrequency, then the log file won't ever be rolled.
*This is strongly discouraged.

意思就是maximumFileSizerollingFrequency 都用于管理滚动(创建新的日志文件)。无论哪个先发生,都会导致日志文件被滚动。官方举例说明了:如果设置rollingFrequency 是 24 小时,但日志日志文件仅在 20 小时后就超过了maximumFileSize。日志文件将在该 20 小时标记处滚动将创建一个新的日志文件,并重新启动 24 小时计时器。反之也是一样。如果24小时没有达到 maximumFileSize,也会创建一个新的日志文件重新计算。我们可以将 maximumFileSize 设置为零来选择禁用由于文件大小而导致的滚动,滚动仅基于rollingFrequency。同样也可以通过将 rollingFrequency 设置为零(或任何非正数)来选择性地禁用滚动。这样做,滚动仅基于maximumFileSize。如果同时禁用 maximumFileSizerollingFrequency,日志文件将永远不会被滚动。官方强烈建议不要这样做。

日志路径与文件名称的自定义

上面说到默认的log日志在沙盒的Library/Caches/Logs目录中,是一个以.log为后缀的文本文件。我们现在项目需要在系统的文件APP

系统文件APP

中可以看到然后可以分享出来,那么我们需要做的以下的事情:
1.首先需要将Log的路径改到Document里面。不然将日志文件放在Library里面我们在文件app中是看不到这个日志文件的。

//修改Logs文件夹的位置
NSString *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *logsDirectory = [paths stringByAppendingPathComponent:@"Logs"];
DDLogFileManagerDefault *defaultManager = [[DDLogFileManagerDefault alloc] initWithLogsDirectory:logsDirectory];
DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:defaultManager];

在创建DDFileLogger的时候可以将DDLogFileManagerDefault设置好路径后给DDFileLogger初始化带上这个值就可以改变文件默认存储的位置。
2.要想在文件APP显示出来日志文件还需要Info plist里面设置一下:Supports Document Browser 为 YES 或者 设置 Application supports iTunes file sharingSupports opening documents in place这两个为YES,然后就可以在文件中查看到日志文件:在文件APP选择我的iPhone->your app文件夹->Logs就可以看到日志文件了。这里的Logs是我自己在

NSString *logsDirectory = [paths stringByAppendingPathComponent:@"Logs"];

这里设置的,随自己定义路径,可以加层级也可以直接放在Document下面。文件如图所示


文件APP中查看的日志文件

选中日志文件长按然后点击共享就可以分享到微信等APP了。

为了用户更好的明白这个日志文件,我们可以修改日志的文件名称。默认的log名字是 ,例如com.CocoaLumberJack.DDLogDemo2021-08-12 13-48.log。修改日志文件名称官方也作了说明:

*If you wish to change default filename, you can override following two methods.
*- newLogFileName method would be called on new logfile creation.
*- isLogFile method would be called to filter log files from all other files in logsDirectory.
*You have to parse given filename and return YES if it is logFile.

意思就是如果想要改变文件名称需要重写newLogFileNameisLogFile这两个方法是在DDLogFileManagerDefault这个类里面,所以新建了一个类DDLogFileManagerSub继承这个DDLogFileManagerDefault然后重写这两个方法

#import "DDLogFileManagerSub.h"

@implementation DDLogFileManagerSub

- (NSString *)newLogFileName{
    NSString *disPlayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
    NSString *timeStamp = [self getTimeStamp];
    return [NSString stringWithFormat:@"%@ %@.log", disPlayName, timeStamp];
}

- (BOOL)isLogFile:(NSString *)fileName{
    BOOL hasProperSuffix = [fileName hasSuffix:@".log"];
    return hasProperSuffix;
}

- (NSString *)getTimeStamp{
    static dispatch_once_t onceToken;
    static NSDateFormatter *dateFormatter;
    dispatch_once(&onceToken, ^{
        dateFormatter = [NSDateFormatter new];
        [dateFormatter setDateFormat:@"YYYY.MM.dd-HH.mm.ss"];
    });
    return [dateFormatter stringFromDate:NSDate.date];
}

@end

- (NSString *)newLogFileName就会返回一个新的日志文件名称,自已定义,我是用app名称和时间戳拼接成文件名。然后我们还需要做一件事就是把之前设置fileLogger

DDLogFileManagerDefault *defaultManager = [[DDLogFileManagerDefault alloc] initWithLogsDirectory:logsDirectory];
DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:defaultManager];

第一句改成

DDLogFileManagerDefault *defaultManager = [[DDLogFileManagerSub alloc] initWithLogsDirectory:logsDirectory];
DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:defaultManager];

这样生成日志文件之后文件名就会变成下图


改变后的日志文件名称
日志格式自定义

打开上面的日志文件可以看到打印的日志内容,如图

日志内容与时间

但我想看更详细的格式以方便定位问题,比如说函数的名称,行数等等。其中还有个问题就是框架里给我返回的时间戳是少了8个小时的。这里我用的是模拟器左上角的时间没有用24小时制的时间是下午3点42,而日志格式的时间是差了8个小时的(不知道你们用这个框架的时候会不会有这个问题,反正我是遇到了)问题先放会,后面会说解决方法,先说如何自定义日志输出格式。框架很齐全,只需要让之前创建的管理类DDLogWrapper遵守DDLogFormatter协议,然后实现一个协议方法- (NSString *)formatLogMessage:(DDLogMessage *)logMessage如下

- (NSString *)formatLogMessage:(DDLogMessage *)logMessage{
    NSString *formatLog = [NSString stringWithFormat:@"%@%@ line:%ld %@",logMessage->_timestamp, logMessage->_function,logMessage->_line,logMessage->_message];
    return formatLog;
}

这里看下这个DDLogMessage的成员变量

@interface DDLogMessage : NSObject 
{
    // Direct accessors to be used only for performance
    @public
    NSString *_message;
    DDLogLevel _level;
    DDLogFlag _flag;
    NSInteger _context;
    NSString *_file;
    NSString *_fileName;
    NSString *_function;
    NSUInteger _line;
    #if DD_LEGACY_MESSAGE_TAG
    id _tag __attribute__((deprecated("Use _representedObject instead", "_representedObject")));;
    #endif
    id _representedObject;
    DDLogMessageOptions _options;
    NSDate * _timestamp;
    NSString *_threadID;
    NSString *_threadName;
    NSString *_queueLabel;
    NSUInteger _qos;
}

我用了几个参数:timestampfuctionline以及打印内容message.分别表示时间戳方法行数打印内容 大家可以根据自己需求增减打印参数,到这里还要把这个formatter赋值给DDFileLogger,然后为了解决时间问题我在协议方法调整了DDLogMessagetimestamp完整的demo如下

- (NSString *)formatLogMessage:(DDLogMessage *)logMessage{
    logMessage->_timestamp = [self getLocalDate];
    NSString *formatLog = [NSString stringWithFormat:@"%@%@ line:%ld %@",logMessage->_timestamp, logMessage->_function,logMessage->_line,logMessage->_message];
    return formatLog;
}
// 调整timestamp
- (NSDate *)getLocalDate{
    NSDate *date = [NSDate date];
    NSTimeZone *zone = [NSTimeZone systemTimeZone];
    NSInteger interval = [zone secondsFromGMTForDate: date];
    NSDate *localDate = [date dateByAddingTimeInterval: interval];
    return localDate;
}

然后将formatter赋值给fileLogger

// 设置日志格式
 fileLogger.logFormatter = [[self alloc] init];

这里的self就是DDLogWrapper 因为他遵守了DDLogFormatter协议以及实现了协议方法所以可以生成新的formatter。看下新的打印日志格式:

修改后的日志格式

现在时间也对了,想要的看的东西也有了。但是这个集成方式有个缺点,我是在公用类里面加的类方法实现打印方式,所以所有的log都是在这个类里面实现的打印的行数和类名自然也都是这个类的, 所以定位不到别的类打印日志的位置和某个其他类的方法,所以我又调研了下CocoaLumberjack这个日志库 发现其实是有swift的扩展的,可以参考我的下一个文章Swift集成CocoaLumberJack日志库(二),会比本篇边的更简洁更明了。

你可能感兴趣的:(Swift集成CocoaLumberJack日志库(一))