Rust 日誌系統實踐總結
文件列表見:ofollow,noindex">Rust 移動端跨平臺複雜圖形渲染專案開發系列總結(目錄)
(上次更新:2018-11-22)基於log、env_logger、fern等的使用總結,詳細配置建議參考官方說明。
給工程新增第三方日誌庫依賴
給Cargo.toml檔案加上如下配置,log基本為Rust專案日誌需求的標配庫,env_logger提供了具體實現,類似策略模式:log定義操作,env_logger實現具體行為,很方便切換另一個實現了log所定義介面的庫,比如daboross/fern 。
[dependencies] log = "0.4.0" env_logger = "0.6.0" 複製程式碼
env_log配置
下面描述我們專案對env_log所作的配置。
配置輸出時間為本地時間
env_logger預設用0時區,而北京是東8區,每次日誌輸出都少8小時,時間沒對上不方便分析日誌。0時區舉個例子:
[2018-11-18T02:00:08Z INFOwebgpu-native::registry] env_logger initialized. 複製程式碼
下面給出env_logger輸出本地時間的示例程式碼,參考了DCjanus/nabu
,他用flexi_logger,略調整即可用於env_logger。
加上更多自定義資訊的關鍵是修改writeln!
巨集。
#[macro_use] extern crate log; extern crate chrono; extern crate env_logger; fn init_log() { use chrono::Local; use std::io::Write; let env = env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "trace"); env_logger::Builder::from_env(env) .format(|buf, record| { writeln!( buf, "{} {} [{}] {}", Local::now().format("%Y-%m-%d %H:%M:%S"), record.level(), record.module_path().unwrap_or("<unnamed>"), &record.args() ) }) .init(); info!("env_logger initialized."); } 複製程式碼
以上程式碼需在fn main()開始或lazy_static開始,否則剛開始部分日誌不受新配置影響。放在lazy_static的日誌配置需要手動 啟用,比如
// 定義 lazy_static! { pub(crate) static ref HUB: Hub = { init_log(); Hub::default() }; } // 在某個入口函式中先訪問HUB,“強迫”它執行lazy_static程式碼塊 fn entry_point() { &*HUB; // “強迫”執行lazy_static程式碼塊 HUB.some_method(); // 裡面的info!()等可正常輸出到檔案或控制檯 } 複製程式碼
以東8區為例,執行顯示:
// 修改前 [2018-11-18T02:00:08Z INFOwebgpu-native::registry] env_logger initialized. // 修改後 2018-11-18 09:27:43 INFO [webgpu-native::registry] env_logger initialized. 複製程式碼
日誌新增行號
writeln!( buf, "{} {} [{}:{}] {}", Local::now().format("%Y-%m-%d %H:%M:%S"), record.level(), record.module_path().unwrap_or("<unnamed>"), record.line().unwrap_or(0), &record.args() ) 複製程式碼
執行顯示:
2018-11-18 10:38:41 INFO [webgpu-native::registry:87] env_logger initialized. 複製程式碼
日誌新增檔名
writeln!( buf, "{} {} [{}:{}:{}] {}", Local::now().format("%Y-%m-%d %H:%M:%S"), record.level(), record.module_path().unwrap_or("<unnamed>"), record.line().unwrap_or(0), &record.args() ) 複製程式碼
執行顯示:
2018-11-18 10:38:48 INFO [webgpu-native::registry:webgpu-native/src/registry.rs:87] env_logger initialized. 複製程式碼
過濾日誌級別
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "trace"); 複製程式碼
filter_or()
可配置如下內建值過濾不同級別的日誌,其實我們也可自行新增filter tag:
- "trace"
- "info"
- "debug"
- "warn"
- "error"
也可以在程式執行時傳遞命令列引數進行過濾:
$ RUST_LOG=info ./main [2018-11-03T06:09:06Z INFOdefault] starting up 複製程式碼
動態過濾資訊
在複雜專案中往往存在多個模組,作為其中一個模組的開發者,為了定位自己負責模組的問題,過濾掉其他模組的日誌是很常見的需求,由於整體專案通常使用同一個日誌庫,逐行註釋其他模組的日誌輸出顯然是不可理的行為。另外,雖然控制檯可以做過濾處理,多條件的過濾規則編寫起來也有難度,而且可能日誌檢視系統不支援這種操作。其實,我們可以給前面一直在修改的format()
加上過濾邏輯,比如:
format(|buf, record| { // special format for debug messages coming from our own crate. if record.level() > log::LevelFilter::Info && record.target() == "my_module" { write!(...) } else if /* some condition */ { write!(...) } else if /* some condition 2*/ { write!(...) } else { write!(...) } } 複製程式碼
過濾邏輯的實現可參考fern/cmd-program.rs 。
fern,env_logger的另一個選擇
Simple, efficient logging for Rust
fern配置起來更直觀(所下所示),目前我還沒測試它與env_logger的效能差異 。
// Configure logger at runtime fern::Dispatch::new() // Perform allocation-free log formatting .format(|out, message, record| { out.finish(format_args!( "{}[{}][{}] {}", chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), record.target(), record.level(), message )) }) // Add blanket level filter - .level(log::LevelFilter::Debug) // - and per-module overrides .level_for("hyper", log::LevelFilter::Info) // Output to stdout, files, and other Dispatch configurations .chain(std::io::stdout()) .chain(fern::log_file("output.log")?) // Apply globally .apply()?; // and log using log crate macros! info!("helllo, world!"); 複製程式碼