基礎認識
mars 是微信官方的終端基礎組件
- C++ 編寫(為了兼容多平臺)
- 業務性無關,平臺性無關的基礎組件
- 支持接入 Android 或者 iOS/OS X 或者 Windows
mars架構 - comm:可以獨立使用的公共庫,包括 socket、線程、消息隊列、協程等;
- xlog:高可靠性高性能的運行期日志組件;
- SDT: 網絡診斷組件;
- STN: 信令分發網絡模塊,也是 Mars 最主要的部分。
優勢
對于終端設備來說,打日志并不只是把日志信息寫到文件里這么簡單。除了流暢性,** 完整性, 容錯性,還有一個最重要的是安全性**。基于不怕被破解,但也不能任何人都能破解的原則, 對日志的規范比加密算法的選擇更為重要,所以這里并沒有討論這一點。
一個優秀的終端日志模塊無論怎么設計都必須做到:
- 不能把用戶的隱私信息打印到日志文件里,不能把日志明文打到日志文件里。
- 不能影響程序的性能。最基本的保證是使用了日志不會導致程序卡頓。
- 不能因為程序被系統殺掉,或者發生了 crash,crash 捕捉模塊沒有捕捉到導致部分時間點沒有日志, 要保證程序整個生命周期內都有日志。
- 不能因為部分數據損壞就影響了整個日志文件,應該最小化數據損壞對日志文件的影響。
上面這幾點也即一直強調的 安全性 流暢性 完整性 容錯性, 它們之間存在著矛盾關系:
- 如果直接寫文件會卡頓,但如果使用內存做中間 buffer 又可能丟日志
- 如果不對日志內容進行壓縮會導致 IO 卡頓影響性能,但如果壓縮,部分損壞可能會影響整個壓縮塊,而且為了增大壓縮率集中壓縮又可能導致 CPU 短時間飆高。
mars 的日志模塊 xlog 就是在兼顧這四點的前提下做到:高性能高壓縮率、不丟失任何一行日志、避免系統卡頓和 CPU 波峰。
使用(Android)
gradle 接入我們提供了兩種接入方式:mars-wrapper 或者 mars-core。如果你只是想做個 sample 推薦使用 mars-wrapper,可以快速開發;但是如果你想把 mars 用到你的 app 中的話,推薦使用 mars-core,可定制性更高。
mars-wrapper
在 app/build.gradle 中添加 mars-wrapper 的依賴:
dependencies {
compile 'com.tencent.mars:mars-wrapper:1.2.0'
}
或者
mars-core
在 app/build.gradle 中添加 mars-core 的依賴:
dependencies {
compile 'com.tencent.mars:mars-core:1.2.2'
}
或者
mars-xlog
如果只想使用 xlog,可以只加 xlog 的依賴(mars-core,mars-wrapper 中都已經包括 xlog):
dependencies {
compile 'com.tencent.mars:mars-xlog:1.0.7'
}
接著往下操作之前,請先確保你已經添加了 mars-wrapper 或者 mars-core 或者 mars-xlog 的依賴
Xlog Init
在程序啟動加載 Xlog 后緊接著初始化 Xlog。但要注意如果你的程序使用了多進程,不要把多個進程的日志輸出到同一個文件中,保證每個進程獨享一個日志文件。而且保存 log 的目錄請使用單獨的目錄,不要存放任何其他文件防止被 xlog 自動清理功能誤刪。
System.loadLibrary("c++_shared");
System.loadLibrary("marsxlog");
final String SDCARD = Environment.getExternalStorageDirectory().getAbsolutePath();
final String logPath = SDCARD + "/marssample/log";
// this is necessary, or may crash for SIGBUS
final String cachePath = this.getFilesDir() + "/xlog"
//init xlog
if (BuildConfig.DEBUG) {
Xlog.appenderOpen(Xlog.LEVEL_DEBUG, Xlog.AppenderModeAsync, cachePath, logPath, "MarsSample", 0, "");
Xlog.setConsoleLogOpen(true);
} else {
Xlog.appenderOpen(Xlog.LEVEL_INFO, Xlog.AppenderModeAsync, cachePath, logPath, "MarsSample", 0, "");
Xlog.setConsoleLogOpen(false);
}
Log.setLogImp(new Xlog());
程序退出時關閉日志:
Log.appenderClose();
如果你想修改 Xlog 的加密算法或者長短連的加解包部分甚至更改組件的其他部分,可以參考這里
Log生成完畢后,會在指定的路徑下生成相應的日志文件:
shell@R7:/sdcard/marssample/log $ ll
-rw-rw---- root sdcard_r 153600 2016-12-30 17:06 MarsSample.mmap2
-rw-rw---- root sdcard_r 29633 2016-12-30 17:06 MarsSample_20161230.xlog
其中MarsSample.mmap2是緩存文件,不用關心,我們需要的是.xlog文件,我們把這個文件pull出來,使用Mars提供的Python腳本進行解密。
找到Mars源碼log/crypt/decode_mars_log_file.py下的這個文件,執行:
? mars_xlog_sdk python decode_mars_log_file.py ~/Downloads/log/MarsSample_20161230.xlog
即可生成對應的log文件,用Sublime即可打開
常用 API 說明
Xlog.java
public static native void appenderOpen(int level, int mode, String cacheDir, String logDir, String nameprefix, int cacheDays, String pubkey);
初始化日志需要調用的接口。
-
level: 日志級別,變量見 Xlog.java 里
LEVEL_XX
, Debug版本推薦LEVEL_DEBUG
, Release 版本推薦LEVEL_INFO
。 -
mode : 文件寫入模式,分異步和同步,變量定義見 Xlog.java 里
AppednerModeXX
, Release版本一定要用AppednerModeAsync
, Debug 版本兩個都可以,但是使用 AppednerModeSync 可能會有卡頓。 - cacheDir : 緩存目錄,當 logDir 不可寫時候會寫進這個目錄,可選項,不選用請給 "", 如若要給,建議給應用的 /data/data/packname/files/log 目錄。
- logDir : 日志寫入目錄,請給單獨的目錄,除了日志文件不要把其他文件放入該目錄,不然可能會被日志的自動清理功能清理掉。
- nameprefix : 日志文件名的前綴,例如該值為TEST,生成的文件名為:TEST_20170102.xlog。
- cacheDays : 一般情況下填0即可。非0表示會在 _cachedir 目錄下存放幾天的日志。
-
pubkey : 加密所用的 pub_key,具體可參考 Xlog 加密指引。
public static native void setConsoleLogOpen(boolean isOpen);
- 是否會把日志打印到 logcat 中, 默認不打印。
- isOpen : true 為打印,false為不打印。
Log.java
該文件包含打印日志最常調用的接口。
public static void setLogImp(LogImp imp)
設置 Log 的具體實現,這里必須調用 Log.setLogImp(new Xlog());
日志才會寫到 Xlog 中。
public static void appenderFlush(boolean isSync)
當日志寫入模式為異步時,調用該接口會把內存中的日志寫入到文件。
- isSync : true 為同步 flush,flush 結束后才會返回。 false 為異步 flush,不等待 flush 結束就返回。強制將緩存中的輸出流(字節流,字符流等)強制輸出.
public static void appenderClose()
關閉日志,在程序退出時調用。
public static void f/e/w/i/d(final String tag, final String msg)
寫日志時調用的接口,對應不同級別的日志。
問題
重要技術點
技術實現思路:使用流式方式對單行日志進行壓縮,壓縮加密后寫進作為 log 中間 buffer的 mmap 中
技術水準有多牛逼,就看這個
mmap
如果大家對binder的實現有看過就知道,binder就是使用mmap來實現一次拷貝的跨進程的。
mmap 是使用邏輯內存對磁盤文件進行映射,中間只是進行映射沒有任何拷貝操作,避免了寫文件的數據拷貝。操作內存就相當于在操作文件,避免了內核空間和用戶空間的頻繁切換。
等于寫內存的速度:把512 Byte的數據分別寫入150 kb大小的內存和 mmap,以及磁盤文件100w次并統計耗時
xlog實現方案:先壓縮再加密效率比較高,這個順序不能改變。而且在寫入 mmap 之前先進行壓縮,也會減少所占用的 mmap 的大小,進而減少 mmap 所占用內存的大小。就是其他模塊每寫一行日志日志模塊就必須進行壓縮。
壓縮方案
第三種是把整個 app 生命周期作為一個壓縮單位進行壓縮,如果這個壓縮單位中有數據損壞,那么后面的日志也都解壓不出來。
但其實在短語式壓縮過程中,滑動窗口并不是無限大的,一般是 32kb ,所以只需要把一定大小作為一個壓縮單位就可以了。這也就是第四個方案, 這樣的話即使壓縮單位中有部分數據損壞,因為是流式壓縮,并不影響這個單位中損壞數據之前的日志的解壓,只會影響這個單位中這個損壞數據之后的日志。
使用流式壓縮后,和之前使用通用壓縮的的日志方案進行了對比(耗時為單行日志的平均耗時):
多條日志同時壓縮會導致 CPU 曲線短時間內極速升高,進而可能會導致程序卡頓,而流式壓縮是把時間分散在整個生命周期內,CPU 的曲線更平滑,相當于把壓縮過程中使用的資源均分在整個 app 生命周期內。雖說時間上升了,但是分攤后CPU曲線更為平滑,實際上是性能的提升。
參考內容
github Mars 項目
mars Android 接入指南
聊聊微信 Xlog
【Dev Club 分享】微信mars 的高性能日志模塊 xlog