寫程序出 Bug 是不可避免的事情夏块,沒有哪一個人的邏輯在每時每刻都是正確無誤的疏咐。很多時候,改 Bug 的時間比寫代碼的時間還長脐供。你偶爾也會懷疑到底自己是在寫 Bug 還是寫程序~
我甚至認為浑塞,程序員排查 Bug 的能力在某種程度上決定了他的技術(shù)水平。通常我們會從控制臺打印出的日志找出程序崩潰的具體位置政己,然后斷點調(diào)試酌壕,一步一步找到元兇掏愁。本文將教讀者使用 logger 這個日志打印庫,極大的加快你排查問題的速度卵牍。
如果你在 Flutter 中仍在使用 print()果港,debugPrint() 打印日志,我覺得是時候了解 logger 這個日志組件了糊昙,因為它真的很優(yōu)雅辛掠。
logger 簡單介紹
logger 是一個純 dart 語言編寫的日志打印庫,不依賴于特定平臺释牺。它非常輕巧且可擴展非常強萝衩,打印出來的日志特別的漂亮,它完美的實現(xiàn)了堆棧信息的自定義打印没咙,多樣的打印器猩谊、過濾器。你可以將日志打印到控制臺祭刚,輸出到文件牌捷,臨時保存到內(nèi)存中等。
我使用這個日志打印組件已經(jīng)有一段時間了涡驮,整體感覺非常的穩(wěn)定暗甥,我特別喜歡它可以打印出方法的堆棧信息,其次作為一個有些許顏控的人捉捅,它打印出的日志格式和顏色我都非常喜歡淋袖。這個組件的作者是居住在德國慕尼黑的一個小伙,他說這個組件的靈感來源于 Android 平臺的日志組件 logger锯梁。
logger 的基本使用和封裝
首先來看看 logger 項目的整體代碼結(jié)構(gòu),由三大部分組成焰情。層次非常的清晰陌凳,作者將類的繼承和對象的組合發(fā)揮到了極致,類名讓人一眼看上去就知道是什么意思内舟,每個類都做到了職責(zé)單一合敦,將業(yè)務(wù)抽象成了代碼,可見作者的代碼水平非常的高验游。filters 目錄中的是過濾器充岛,outputs 輸出器指定日志輸出位置,printers 打印器規(guī)定了日志打印的樣式和堆棧信息等耕蝉。
基本使用
logger 的使用非常的簡單崔梗,在 pubspec.yaml
中添加如下依賴。
logger: ^1.0.0
它的日志級別分為7個垒在,如下所示蒜魄。默認的日志級別是 verbose,即會打印出所有 >= verbose 級別的日志。
enum Level {
verbose,
debug,
info,
warning,
error,
wtf,
nothing,
}
當(dāng)你要打印日志的時候谈为,只需實例化一個 Logger 對象旅挤,然后調(diào)用不同級別的打印方法就可以了。
void main() {
var _logger = Logger(
printer: PrettyPrinter(
methodCount: 0,
),
);
_logger.v('verbose message');
_logger.d('debug message');
_logger.i('info message');
_logger.w('warning message');
_logger.e('error message');
_logger.wtf('wft message');
}
下圖是上面代碼所打印出來的效果伞鲫。
logger 除了使用簡單之外粘茄,輸出的日志也很優(yōu)美。在 Logger 的構(gòu)造函數(shù)中秕脓,我們可以傳入特定的打印器柒瓣、過濾器、輸出位置等參數(shù)自由配置撒会,下面是 Logger 的構(gòu)造函數(shù)嘹朗。
Logger({
LogFilter? filter, // 過濾器,可以區(qū)分開發(fā)環(huán)境與生產(chǎn)環(huán)境
LogPrinter? printer, // 打印器诵肛,控制日志輸出樣式和堆棧信息等
LogOutput? output, // 輸出器屹培,控制日志輸出位置≌荩可以是控制臺褪秀、文件、內(nèi)存
Level? level,
}) : _filter = filter ?? DevelopmentFilter(),
_printer = printer ?? PrettyPrinter(),
_output = output ?? ConsoleOutput() {
_filter.init();
_filter.level = level ?? Logger.level;
_printer.init();
_output.init();
}
如果不傳入任何參數(shù)薛训,默認過濾器是開發(fā)者模式媒吗,打印器是漂亮的打印器、輸出位置是控制臺乙埃。
簡單封裝
打印日志在項目中是全局的闸英,為了能在項目中任意地方使用打印功能,最好封裝一下介袜,如下是一個簡單的封裝甫何,Logger 只需要實例化一次,之后在項目中任何地方都可以調(diào)用各個級別的打印方法遇伞。這里我使用了 PrefixPrinter 打印器包裝了 PrettyPrinter 打印器辙喂。
class Log {
static Logger _logger = Logger(
printer: PrefixPrinter(PrettyPrinter()),
);
static void v(dynamic message) {
_logger.v(message);
}
static void d(dynamic message) {
_logger.d(message);
}
static void i(dynamic message) {
_logger.i(message);
}
static void w(dynamic message) {
_logger.w(message);
}
static void e(dynamic message) {
_logger.e(message);
}
static void wtf(dynamic message) {
_logger.wtf(message);
}
}
logger 的打印器
logger 的打印器是 logger 目前最核心的功能,本文會重點講解打印器鸠珠。以 PrettyPrinter() 打印器為例巍耗,首先看一下它的構(gòu)造函數(shù),如下渐排。
PrettyPrinter({
this.stackTraceBeginIndex = 0, // 方法棧的開始下標
this.methodCount = 2, // 打印方法棧的個數(shù)
this.errorMethodCount = 8, // 自己傳入方法棧對象后該參數(shù)有效
this.lineLength = 120, // 每行最多打印的字符個數(shù)
this.colors = true, // 日志是否有顏色
this.printEmojis = true,// 是否打印 emoji 表情
this.printTime = false, // 是否打印時間
})
使用 PrettyPrinter 不指定任何參數(shù)炬太,默認的打印方式如上,接著用我們上面剛剛封裝好的代碼驯耻。打印看看效果娄琉。
// LogTest.dart
void main(){
Log.w("PrettyPrinter warning message");
}
在
LogTest.dart
的 main 方法中打印了一條 warning 級別的日志次乓,因為沒有指定 PrettyPrinter 的任何參數(shù),所以打印出的棧方法默認是#0
和#1
兩條孽水。讀者應(yīng)該知道調(diào)用方法其實是不停的在向系統(tǒng)壓棧票腰,最后調(diào)用的方法肯定在棧頂,很顯然女气,#0
是棧頂杏慰。那么棧底調(diào)用的方法是哪個呢?其實讀者只要指定打印的方法棧個數(shù)足夠大炼鞠,就可以看到了缘滥。
不知道讀者有沒有發(fā)現(xiàn)一個問題,我們封裝后谒主,每次打印的日志都會攜帶一條 #0
的方法棧日志朝扼。大多時候我們不關(guān)心封裝里面的方法調(diào)用,只關(guān)心這條日志是從哪打印的(上面是#1
)霎肯,這樣就可以快速定位到對應(yīng)代碼的位置擎颖。
現(xiàn)在,思考如何將#0
去除观游?其實也很簡單搂捧,通過查看源碼。我們只要指定 stackTraceBeginIndex 和 methodCount 的值懂缕,就可以控制輸出了≡逝埽現(xiàn)在為 PrettyPrinter 指定這兩個參數(shù)的值,分別是 5 和 1搪柑。
static Logger _logger = Logger(
printer: PrefixPrinter(PrettyPrinter(
stackTraceBeginIndex: 5,
methodCount: 1
)),
);
為什么 stackTraceBeginIndex 的值是 5 呢聋丝。讀者可以查看 PrettyPrinter 類中 formatStackTrace 方法,斷點調(diào)試查看方法棧信息即可得到具體的值工碾。
之后再次打印日志潮针,就只有剛才的
#1
棧方法會被打印了。
logger 的過濾器
logger 目前有兩種過濾器 DevelopmentFilter 和 ProductionFilter倚喂。使用 DevelopmentFilter 在 debug mode 時日志都會被打印。如果你將 APK 打 Release 包時瓣戚,所有日志都將不會打印端圈。
而使用 ProductionFilter,無論是 debug mode 還是 將 APK 打 Release 包放入生產(chǎn)環(huán)境子库,日志都將會打印舱权。
logger 是如何實現(xiàn)這種功能的呢?通過查看其源碼仑嗅,也非常的簡單巧妙宴倍。下面是 DevelopmentFilter 的實現(xiàn)张症,由于 assert 斷言語句只有在 debug mode 時才會被調(diào)用,所以 shouldLog = true鸵贬,日志就可以打印了俗他。在生產(chǎn)環(huán)境 assert 斷言語句是不執(zhí)行的,這樣就屏蔽了所有日志的輸出阔逼。
class DevelopmentFilter extends LogFilter {
@override
bool shouldLog(LogEvent event) {
var shouldLog = false;
assert(() { // assert 斷言只有在 debug mode 才會被調(diào)用兆衅。
if (event.level.index >= level!.index) {
shouldLog = true;
}
return true;
}());
return shouldLog;
}
}
logger 的輸出器
logger 充分考慮到了用戶的使用場景,支持日志打印在控制臺嗜浮、文件羡亩、內(nèi)存。甚至可以使用 MultiOutput 輸出器將日志同時輸出在多個位置危融。這里就不詳細講解這些 API 的使用方法畏铆,讀者可以自行嘗試。
彩色日志的實現(xiàn)原理
這個項目最吸引我的一個點吉殃,就是它打印出來的日志真的很好看辞居!顏色分明,看上去特別的舒服寨腔。不知道你是否也好奇控制臺是如何輸出這些彩色日志的速侈?
這必須談到 ANSI 轉(zhuǎn)義序列,通過它就可以控制文本在終端上的光標位置迫卢、顏色和其他選項倚搬。一個標準的 ANSI 轉(zhuǎn)義序列以 ASCII 碼值 31 加上一個左方括號組成。因為 31 的 16 進制表示是 x1B
乾蛤,所以轉(zhuǎn)義之后最終就是這樣子:\x1B[
每界。左方括號[
后面就可以指定具體的輸出模式了,比如你想讓helloworld
這個單詞輸出顏色為紅色家卖,那么整個字符串序列就是這樣的眨层。其中31m
指定輸出到控制臺的顏色為紅色。
"\x1B[31m helloworld"
關(guān)于 ANSI 轉(zhuǎn)義序列的更多輸出模式和使用方法上荡,讀者可以查閱相關(guān)資料進一步了解趴樱。在 logger 組件中,AnsiColor
這個類實現(xiàn)了讓不同級別的日志呈現(xiàn)出不同顏色的效果酪捡。
寫在最后
本文介紹了 logger 日志組件的詳細使用方法叁征。向讀者介紹了 logger 的打印器、過濾器逛薇、輸出器捺疼。對其參數(shù)和可能出現(xiàn)疑惑的地方進行了詳細的說明。并在最后揭開了如何打印彩色日志的原理永罚。讀者看完全文啤呼,應(yīng)該有一種豁然開朗的感覺卧秘,其實一個日志組件的基本組成就是這樣。由于 logger 組件的可擴展性非常的強官扣,我們完全可以通過繼承 logger 的基類來實現(xiàn)自己的打印器翅敌、過濾器和輸出器,全文完醇锚。
如果你對我感興趣哼御,請移步到 http://blogss.cn ,
或關(guān)注公眾號:程序員小北焊唬,進一步了解恋昼。
- 如果本文幫助到了你,歡迎點贊和關(guān)注赶促,這是我持續(xù)創(chuàng)作的動力 ??
- 由于作者水平有限液肌,文中如果有錯誤,歡迎在評論區(qū)指正 ??
- 本文首發(fā)于掘金鸥滨,未經(jīng)許可禁止轉(zhuǎn)載 ??