Flutter | 日志還能這么打印骚揍,太秀了!

寫程序出 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 代碼結(jié)構(gòu)

基本使用

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)載 ??
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嗦哆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子婿滓,更是在濱河造成了極大的恐慌老速,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凸主,死亡現(xiàn)場離奇詭異橘券,居然都是意外死亡,警方通過查閱死者的電腦和手機卿吐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門旁舰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嗡官,你說我怎么就攤上這事箭窜。” “怎么了衍腥?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵磺樱,是天一觀的道長。 經(jīng)常有香客問我婆咸,道長竹捉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任擅耽,我火速辦了婚禮,結(jié)果婚禮上物遇,老公的妹妹穿的比我還像新娘乖仇。我一直安慰自己憾儒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布乃沙。 她就那樣靜靜地躺著起趾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪警儒。 梳的紋絲不亂的頭發(fā)上训裆,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音蜀铲,去河邊找鬼边琉。 笑死,一個胖子當(dāng)著我的面吹牛记劝,可吹牛的內(nèi)容都是我干的变姨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼厌丑,長吁一口氣:“原來是場噩夢啊……” “哼定欧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起怒竿,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤砍鸠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耕驰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爷辱,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年耍属,在試婚紗的時候發(fā)現(xiàn)自己被綠了托嚣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡厚骗,死狀恐怖示启,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情领舰,我是刑警寧澤夫嗓,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站冲秽,受9級特大地震影響舍咖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锉桑,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一排霉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧民轴,春花似錦攻柠、人聲如沸球订。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冒滩。三九已至,卻和暖如春浪谴,著一層夾襖步出監(jiān)牢的瞬間开睡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工苟耻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留篇恒,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓梁呈,卻偏偏與公主長得像婚度,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子官卡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容