一、前言
日志不僅記錄了程序的執(zhí)行過(guò)程,同時(shí)也是分析問(wèn)題的一種重要手段。
對(duì)于神策分析 iOS SDK 而言,通過(guò)日志系統(tǒng)不但可以了解到 SDK 的行為,而且便于我們排查問(wèn)題。因此,日志系統(tǒng)是 SDK 中必不可少的一項(xiàng)功能。
下面針對(duì)神策分析 iOS SDK 日志系統(tǒng)進(jìn)行解析,希望能夠給大家提供一些參考。
二、日志打印方式
對(duì)于 iOS 開(kāi)發(fā)而言,在控制臺(tái)打印日志的常用方式有 NSLog 和 printf,我們先來(lái)看一下兩者的區(qū)別。
2.1NSLog
NSLog 是 Foundation 框架提供的日志輸出函數(shù),可以在控制臺(tái)進(jìn)行格式化輸出。
日志的內(nèi)容會(huì)自動(dòng)包含一些系統(tǒng)信息,例如:項(xiàng)目名稱(chēng)、時(shí)間等。另外,NSLog 還可以打印 OC 中的對(duì)象,并且輸出的內(nèi)容會(huì)自動(dòng)換行。示例代碼如下:

2.2. printf
printf 只能打印 char* 類(lèi)型,并且內(nèi)容不會(huì)自動(dòng)換行,需要我們手動(dòng)操作[1]。示例代碼如下:

2.3. 小結(jié)
通過(guò)前面的介紹我們不難看出,在 iOS 平臺(tái)上通過(guò) NSLog 輸出日志更有優(yōu)勢(shì)。既然如此,神策為什么不選用 NSLog 來(lái)進(jìn)行日志輸出呢?主要原因如下:
1、NSLog 只有簡(jiǎn)單的控制臺(tái)打印輸出,沒(méi)有其他的輸出方式;
2、NSLog 打印格式相對(duì)單一,不夠靈活,又無(wú)法自定義和擴(kuò)展;
3、NSLog 打印效率不高,而且有長(zhǎng)度限制;
4、如果客戶(hù)重寫(xiě)了 NSLog,那么日志可能無(wú)法打印到控制臺(tái),影響問(wèn)題定位。
為了解決上述問(wèn)題,神策開(kāi)發(fā)了自己的日志系統(tǒng)(SALog)來(lái)進(jìn)行日志輸出。
3. SALog 的實(shí)現(xiàn)
SALog 是一個(gè)日志系統(tǒng),具有較好的擴(kuò)展性和易用性,下面我們來(lái)看下 SALog 具體是如何實(shí)現(xiàn)的。
3.1. 原理簡(jiǎn)介
目前 SALog 中最重要的功能就是在控制臺(tái)輸出日志(通過(guò)子類(lèi) SAConsoleLogger 實(shí)現(xiàn)),SAConsoleLogger 的主要原理是:實(shí)例化一個(gè) iovec 結(jié)構(gòu)體來(lái)容納數(shù)據(jù),然后通過(guò) writev 函數(shù)來(lái)發(fā)送這些數(shù)據(jù)。
結(jié)構(gòu)體 iovec 包含了 iov_base 和 iov_len 兩個(gè)屬性[2],它們的含義如下:
1、 iov_base 屬性指向一個(gè)緩沖區(qū),存放將要發(fā)送的數(shù)據(jù);
2、 iov_len 屬性記錄了輸出日志的長(zhǎng)度。
核心代碼如下所示:

3.2. 整體設(shè)計(jì)
SALog 是一個(gè)日志系統(tǒng),既可以用于控制臺(tái)日志的打印也可以輸出到本地文件。這樣的日志系統(tǒng)有一套核心的設(shè)計(jì)標(biāo)準(zhǔn):創(chuàng)建一個(gè)基類(lèi)實(shí)現(xiàn)基本的邏輯,具體的模塊通過(guò)繼承基類(lèi)并重寫(xiě)基類(lèi)的方法實(shí)現(xiàn)功能。
這樣的設(shè)計(jì)具有較好的擴(kuò)展性與維護(hù)性,整體的 UML 如圖 3-1 所示:

圖 3-1 日志系統(tǒng)的 UML 圖
通過(guò)上圖可以看到神策定義了 SALogger 協(xié)議,在基類(lèi) SAAbstractLogger 中實(shí)現(xiàn)了基本的邏輯。SAFileLogger(文件日志)、SAConsoleLogger(控制臺(tái)日志)等日志模塊都是通過(guò)繼承 SAAbstractLogger 這個(gè)基類(lèi),在 - logMessage: 方法中實(shí)現(xiàn)各自的功能邏輯。核心代碼如下所示:

3.3. 日志格式
神策定義了 SALogMessageFormatter 協(xié)議,基于此協(xié)議可實(shí)現(xiàn)多種不同的 formatter,便于后期的維護(hù)和擴(kuò)展。例如:SALoggerConsoleFormatter、SALoggerPrePostFixFormatter 等,可以標(biāo)注不同顏色、前后綴、特定標(biāo)識(shí)等。核心代碼如下所示:
//SALoggerConsoleFormatter.m
-
(NSString *)formattedLogMessage:(nonnull SALogMessage *)logMessage {
NSString *prefixEmoji = @"";
NSString *levelString = @"";
switch (logMessage.level) {
case SALogLevelError:
prefixEmoji = @"?";
levelString = @"Error";
break;
······
default:
break;
}······
return [NSString stringWithFormat:@"%@ %@ %@ %@ %@ %@ line:%@ %@\n", dateString, prefixEmoji, levelString, self.prefix, logMessage.fileName, logMessage.function, line, logMessage.message];
3.4. 長(zhǎng)度限制
不同開(kāi)發(fā)者的代碼習(xí)慣是不同的,業(yè)務(wù)邏輯也是不同的,這就導(dǎo)致 SDK 采集的數(shù)據(jù)大小都是不確定的。如果開(kāi)發(fā)人員想要查看采集的數(shù)據(jù)是否正確,就需要保證輸出的日志信息是完整的。為了保證采集的數(shù)據(jù)都能完整打印,SALog 將低于 1024 * 4 Bytes 的日志數(shù)據(jù)存儲(chǔ)在棧區(qū),超過(guò) 1024 * 4 Bytes 的日志數(shù)據(jù)存儲(chǔ)在堆區(qū),以保證打印的完整性。核心代碼如下所示:

3.5. 日志分級(jí)
為了方便開(kāi)發(fā)人員查看和篩選日志,SDK 通過(guò)設(shè)置枚舉類(lèi)型對(duì)日志等級(jí)進(jìn)行劃分,從高到低依次為 Error、Warn、Info、Debug、Verbose,如下所示:

另外,為了便于使用,SALog 通過(guò)宏定義的方式實(shí)現(xiàn)了不同等級(jí)的日志接口:

4. SALog 的使用
SALog 的接口設(shè)計(jì)十分友好,使用起來(lái)也比較方便。只需要在初始化 SDK 時(shí),打開(kāi)日志輸出功能即可:

然后在 Xcode 控制臺(tái)中可篩選關(guān)鍵字進(jìn)行查看:
1、 埋點(diǎn)事件觸發(fā)成功時(shí),SDK 會(huì)輸出【track event】字段開(kāi)頭的事件數(shù)據(jù);
2、 埋點(diǎn)事件觸發(fā)失敗時(shí),SDK 會(huì)輸出相應(yīng)的錯(cuò)誤原因;
3、 事件數(shù)據(jù)上報(bào)成功時(shí),SDK 會(huì)輸出【valid message】字段開(kāi)頭的事件數(shù)據(jù);
4、 事件數(shù)據(jù)上報(bào)失敗時(shí),SDK 會(huì)輸出【invalid message】字段開(kāi)頭的事件數(shù)據(jù)并輸出錯(cuò)誤原因。
通常情況下,我們只希望在 DEBUG 模式下輸出日志。此時(shí),可以通過(guò)宏定義來(lái)解決此問(wèn)題:

如上配置后,SDK 只會(huì)在 DEBUG 模式下輸出日志,這樣避免了線上應(yīng)用因忘記關(guān)掉日志輸出功能而產(chǎn)生的性能影響。
5. 展望未來(lái)
通過(guò) SALog 可以在控制臺(tái)上輸出各種各樣的日志信息,給我們排查線上問(wèn)題帶來(lái)了極大的便利。不過(guò),SALog 并非完美的,還有一些改進(jìn)的空間。
在實(shí)際使用的過(guò)程中,我們收集了一些關(guān)于 SALog 的痛點(diǎn):
1、 當(dāng)前沒(méi)有對(duì)輸出日志的級(jí)別進(jìn)行控制,導(dǎo)致在開(kāi)啟日志的情況下,會(huì)將所有級(jí)別的日志進(jìn)行輸出;
2、 無(wú)法對(duì)于日志內(nèi)容進(jìn)行篩選;
3、目前只支持輸出日志到控制臺(tái)。
針對(duì)這些痛點(diǎn),我們也給出了相應(yīng)的解決方案:
1、 在 SDK 的配置項(xiàng)中增加字段,用于控制輸出日志的級(jí)別;
2、 針對(duì)日志內(nèi)容增加模糊匹配的功能,實(shí)現(xiàn)對(duì)于日志內(nèi)容的篩選;
3、 增加日志輸出的渠道,例如:文件系統(tǒng)、云端等。
目前,這些功能已經(jīng)在逐步研發(fā)中,不久之后大家就可以體驗(yàn)這些功能。
6. 總結(jié)
本文主要介紹了神策分析 iOS SDK 日志系統(tǒng)的實(shí)現(xiàn)和使用,并對(duì)日志系統(tǒng)的未來(lái)進(jìn)行了規(guī)劃。希望通過(guò)這篇文章,大家能夠?qū)θ罩鞠到y(tǒng)的實(shí)現(xiàn)和使用有更深入的了解。