Swift集成CocoaLumberJack日志庫(一)

集成背景

Swift自帶的print打印日志方式確實(shí)是內(nèi)容變簡單了,沒有了時間顯示在調(diào)試某些功能或者bug的時候確實(shí)是個很頭疼的事情。其次簡單的print達(dá)不到我們目前的需求,我們需要APP有收集日志和保存到本地并能夠分享出來的功能。然后基于CocoaLumberJack日志庫的強(qiáng)大選擇了此庫集成日志收集功能。

基本使用

使用cocoapod安裝庫:pod 'CocoaLumberjack' .安裝好之后,打開項目還不能直接使用DDLog的功能,首先因?yàn)槿罩編鞂τ诘母鞣N打印使用Define的方式,swift直接使用不是行的(后面發(fā)現(xiàn)是可以使用swift版本的,會在我的另一篇文章寫出來)。于是我在項目中新建了一個OC的管理類DDLogWrapper去實(shí)現(xiàn)DDLog的打印功能。在swift首次新建的OC類會提醒你創(chuàng)建一個橋接文件點(diǎn)確定就行了,如果是已經(jīng)有的就不需要創(chuàng)建了,創(chuàng)建好類之后放到橋接文件里

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

#import "DDLogWrapper.h"

#endif

在類里面聲明好接口

#import <Foundation/Foundation.h>
#import <CocoaLumberjack/CocoaLumberjack.h>

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

然后在實(shí)現(xiàn)里面調(diào)用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
    }
控制臺打印結(jié)果

現(xiàn)在打印是有日志時間了,但這里會有人會疑惑了,代碼里不是打印了5個嗎?為什么控制臺只輸出了4句,最后一句沒有打印?其實(shí)我們在創(chuàng)建DDLogWrapper的時候定義了一個log的等級,就是這句:

// 定義log等級
static const DDLogLevel ddLogLevel = DDLogLevelDebug;

CocoaLumberjack提供了不同等級的Log輸出,一共有5種等級,以下為5種等級的宏定義。

DDLogError(frmt, ...)      //錯誤      當(dāng)ddLogLevel = DDLogLevelError及以上等級時會輸出
DDLogWarn(frmt, ...)       //警告      當(dāng)ddLogLevel = DDLogLevelWarning及以上等級時會輸出
DDLogInfo(frmt, ...)       //信息      當(dāng)ddLogLevel = DDLogLevelInfo及以上等級時會輸出
DDLogDebug(frmt, ...)      //調(diào)試      當(dāng)ddLogLevel = DDLogLevelDebug及以上等級時會輸出
DDLogVerbose(frmt, ...)    //詳細(xì)      當(dāng)ddLogLevel = DDLogLevelVerbose及以上等級時會輸出

輸出的等級由我們在DDLogWrapper定義的ddLogLevel決定。比如我們上面定義了ddLogLevel = DDLogLevelDebug,那么debug及以下等級的Log都能夠輸出,而使用DDLogVerbose的Log就不會輸出。

logger記錄器

在上面的demo里我們在項目入口初始化了一個DDOSLogger

// 添加logger  下面會說明這個DDOSLogger
 DDLog.add(DDOSLogger.sharedInstance)

CocoaLumberjack內(nèi)置了以下幾種Logger。

DDFileLogger:是將log寫入到文件中。這也是本篇我們著重要講的
DDOSLogger:在iOS10開始使用,在將Log輸出到 控制臺.app 和 Xcode控制臺。
DDASLLogger:將日志寫入到控制臺.app中。在iOS10開始過時
DDTTYLogger:將日志寫入到Xcode控制臺。

所以,想要替換Swift的Print,官方推薦的做法是:
iOS10及以上系統(tǒng)版本,使用DDOSLogger
iOS10以下版本,使用DDASLLogger+DDTTYLogger。

寫入文件

之前介紹了DDFileLogger可以將日志寫入到文件。添加DDFileLogger就可以將日志記錄到文件里面了,跟添加DDOSLogger一樣。

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

默認(rèn)的log日志在沙盒的Library/Caches/Logs目錄中,是一個以.log為后綴的文本文件,如下圖所示。

生成log文件路徑

還可以給DDFileLogger添加各種屬性,下面為屬性設(shè)置都有解釋

+ (void)addFileLogger{
    DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
    //重用log文件,不要每次啟動都創(chuàng)建新的log文件(默認(rèn)值是NO)
    fileLogger.doNotReuseLogFiles = NO;
    //log文件在24小時內(nèi)有效,超過時間創(chuàng)建新log文件(默認(rèn)值是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調(diào)用,如下

DDLogWrapper.addOSLogger()
DDLogWrapper.addFileLogger()

重點(diǎn)解釋下rollingFrequencymaximumFileSize這兩個參數(shù)在官方的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 都用于管理滾動(創(chuàng)建新的日志文件)。無論哪個先發(fā)生,都會導(dǎo)致日志文件被滾動。官方舉例說明了:如果設(shè)置rollingFrequency 是 24 小時,但日志日志文件僅在 20 小時后就超過了maximumFileSize。日志文件將在該 20 小時標(biāo)記處滾動將創(chuàng)建一個新的日志文件,并重新啟動 24 小時計時器。反之也是一樣。如果24小時沒有達(dá)到 maximumFileSize,也會創(chuàng)建一個新的日志文件重新計算。我們可以將 maximumFileSize 設(shè)置為零來選擇禁用由于文件大小而導(dǎo)致的滾動,滾動僅基于rollingFrequency。同樣也可以通過將 rollingFrequency 設(shè)置為零(或任何非正數(shù))來選擇性地禁用滾動。這樣做,滾動僅基于maximumFileSize。如果同時禁用 maximumFileSizerollingFrequency,日志文件將永遠(yuǎn)不會被滾動。官方強(qiáng)烈建議不要這樣做。

日志路徑與文件名稱的自定義

上面說到默認(rèn)的log日志在沙盒的Library/Caches/Logs目錄中,是一個以.log為后綴的文本文件。我們現(xiàn)在項目需要在系統(tǒng)的文件APP

系統(tǒng)文件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];

在創(chuàng)建DDFileLogger的時候可以將DDLogFileManagerDefault設(shè)置好路徑后給DDFileLogger初始化帶上這個值就可以改變文件默認(rèn)存儲的位置。
2.要想在文件APP顯示出來日志文件還需要Info plist里面設(shè)置一下:Supports Document Browser 為 YES 或者 設(shè)置 Application supports iTunes file sharingSupports opening documents in place這兩個為YES,然后就可以在文件中查看到日志文件:在文件APP選擇我的iPhone->your app文件夾->Logs就可以看到日志文件了。這里的Logs是我自己在

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

這里設(shè)置的,隨自己定義路徑,可以加層級也可以直接放在Document下面。文件如圖所示


文件APP中查看的日志文件

選中日志文件長按然后點(diǎn)擊共享就可以分享到微信等APP了。

為了用戶更好的明白這個日志文件,我們可以修改日志的文件名稱。默認(rèn)的log名字是 <bundle identifier><date><time>.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名稱和時間戳拼接成文件名。然后我們還需要做一件事就是把之前設(shè)置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];

這樣生成日志文件之后文件名就會變成下圖


改變后的日志文件名稱
日志格式自定義

打開上面的日志文件可以看到打印的日志內(nèi)容,如圖

日志內(nèi)容與時間

但我想看更詳細(xì)的格式以方便定位問題,比如說函數(shù)的名稱,行數(shù)等等。其中還有個問題就是框架里給我返回的時間戳是少了8個小時的。這里我用的是模擬器左上角的時間沒有用24小時制的時間是下午3點(diǎn)42,而日志格式的時間是差了8個小時的(不知道你們用這個框架的時候會不會有這個問題,反正我是遇到了)問題先放會,后面會說解決方法,先說如何自定義日志輸出格式??蚣芎荦R全,只需要讓之前創(chuàng)建的管理類DDLogWrapper遵守DDLogFormatter協(xié)議,然后實(shí)現(xiàn)一個協(xié)議方法- (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 <NSCopying>
{
    // 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;
}

我用了幾個參數(shù):timestamp、fuction、line以及打印內(nèi)容message.分別表示時間戳、方法、行數(shù)、打印內(nèi)容 大家可以根據(jù)自己需求增減打印參數(shù),到這里還要把這個formatter賦值給DDFileLogger,然后為了解決時間問題我在協(xié)議方法調(diào)整了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;
}
// 調(diào)整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

// 設(shè)置日志格式
 fileLogger.logFormatter = [[self alloc] init];

這里的self就是DDLogWrapper 因?yàn)樗袷亓?code>DDLogFormatter協(xié)議以及實(shí)現(xiàn)了協(xié)議方法所以可以生成新的formatter。看下新的打印日志格式:

修改后的日志格式

現(xiàn)在時間也對了,想要的看的東西也有了。但是這個集成方式有個缺點(diǎn),我是在公用類里面加的類方法實(shí)現(xiàn)打印方式,所以所有的log都是在這個類里面實(shí)現(xiàn)的打印的行數(shù)和類名自然也都是這個類的, 所以定位不到別的類打印日志的位置和某個其他類的方法,所以我又調(diào)研了下CocoaLumberjack這個日志庫 發(fā)現(xiàn)其實(shí)是有swift的擴(kuò)展的,可以參考我的下一個文章Swift集成CocoaLumberJack日志庫(二),會比本篇邊的更簡潔更明了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容