撰寫日誌(logging)是除錯(debug)和事件分析時經常會使用的方式。Rust生態圈有log
這個crate,針對訊息重要性(等級)提供了不同的巨集讓函式庫或是應用程式輸出日誌(log),不過它並沒有限定日誌最終會被輸出到哪,Rust開發者在引入有用到log
這個crate的函式庫後,可以自行決定日誌的輸出方式。
log
將訊息依重要性依序分為trace
、debug
、info
、warn
、error
五個等級,trace
是最不重要的,error
則是最重要的。這五種等級有各自對應的類函數(function-like)巨集(以下稱這些巨集為日誌巨集),用法如下:
#[macro_use]
extern crate log;
pub fn add(a: i32, b: i32) -> i32 {
info!("{a} + {b}");
a + b
}
日誌巨集的撰寫格式和println!
類似,可以參考這篇文章來了解格式化文字的方式。除此之外,日誌巨集還多了標籤的功能,可以對日誌訊息做「分類」的動作,用法如下:
#[macro_use]
extern crate log;
pub fn add(a: i32, b: i32) -> i32 {
trace!(target: "call_function", "add");
info!("{a} + {b}");
a + b
}
如果未在日誌巨集的第一個參數使用target:
來定義標籤名稱,則會將標籤名稱預設為呼叫這個巨集的模組的路徑。例如上面的程式其實等同於:
#[macro_use]
extern crate log;
pub fn add(a: i32, b: i32) -> i32 {
trace!(target: "call_function", "add");
info!(target: core::module_path!(), "{} + {}", a, b);
a + b
}
至此log
這個crate的用法就介紹完了,超簡單。但是如果我們在src/main.rs
中呼叫上面這個add
函數的話,會發現程式並不會輸出任何日誌訊息。
因為我們還需要引用其它的logger實作,才能真正的讓這些日誌巨集發揮其該有的功能。以下會介紹一些常用的logger實作。
env_logger
env_logger
是一個輕巧的logger,顧名思義,它可以透過環境變數在程式執行階段進行組態設定。
基本用法如下:
fn main() {
env_logger::init();
...
}
預設的情況下,env_logger
會將日誌訊息輸出到標準錯誤(stderr),若想改成輸出到標準輸出(stdout),可以將程式改寫如下:
fn main() {
env_logger::builder().target(env_logger::Target::Stdout).init();
...
}
控制env_logger
行為的環境變數為RUST_LOG
,其值的寫法和用途如下:
info
:輸出等級至少為info
的日誌訊息。hello
:輸出hello
這個模組的所有日誌訊息。hello=info
:輸出hello
這個模組中等級至少為info
的日誌訊息。error,hello=info
:輸出等級至少為error
的日誌訊息,但是在hello
這個模組中會輸出等級至少為info
的日誌訊息。error/regex1,hello=info/regex2
:輸出等級至少為error
的日誌訊息,這些訊息要符合regex1代表的正規表示式。但是在hello
這個模組中會輸出等級至少為info
的日誌訊息,這些訊息要符合regex2代表的正規表示式。
RUST_LOG_STYLE
環境變數可以控制env_logger
輸出的訊息要不要帶有顏色或是粗斜體等風格,設成never
可以禁用文字風格。
若想要自訂日誌訊息的格式可以將程式改寫如下:
use std::io::Write;
fn main() {
env_logger::builder()
.format(|buf, record| writeln!(buf, "{}({}): {}", record.level(), record.target(), record.args()))
.init();
...
}
如果要在Rust測試中抓取日誌訊息(為了讓這些訊息只有在測試失敗的時候才會被輸出),在初始化env_logger
時可以使用如下的程式敘述:
env_logger::builder().is_test(true).init();
env_logger
需要透過設定RUST_LOG
環境變數才能真正輸出日誌訊息,如果想要預設RUST_LOG
環境變數的值,程式可以這樣寫:
use std::env;
fn main() {
if env::var("RUST_LOG").is_err() {
env::set_var("RUST_LOG", "debug");
}
env_logger::init();
...
}
pretty_env_logger
pretty_env_logger
是env_logger
的視覺加強版,用法與env_logger
基本上是一樣的。不過pretty_env_logger
並沒有提供builder
函數,而是改用formatted_builder
和formatted_timed_builder
函數,這兩個函數內部會去呼叫env_logger::Builder
的format
方法來設定文字格式。
基本用法如下:
fn main() {
pretty_env_logger::init();
...
}
simplelog
simplelog
提供了三種輕巧的logger,分別是什麼風格都不加、單純將訊息寫入標準輸出或是標準錯誤的SimpleLogger
、有加風格的TermLogger
、能將日誌寫入某個Writer(例如檔案)的WriteLogger
。另外它還提供了CombinedLogger
,讓我們可以同時多個以上提到的logger。
SimpleLogger
的基本用法如下:
fn main() {
simplelog::SimpleLogger::init(simplelog::LevelFilter::Info, simplelog::Config::default()).unwrap();
...
}
LevelFilter
列舉可以設定能輸出的日誌訊息之最小等級。對於error
等級的日誌訊息,SimpleLogger
會將其寫進標準錯誤,其餘等級的日誌訊息會寫入標準輸出。
TermLogger
的基本用法如下:
fn main() {
simplelog::TermLogger::init(simplelog::LevelFilter::Info, simplelog::Config::default(), simplelog::TerminalMode::Stderr).unwrap();
...
}
TerminalMode
列舉可以設定TermLogger
要將日誌訊息輸出到標準輸出或標準錯誤。或者可以使用其Mixed
變體,將error
等級的日誌訊息寫進標準錯誤,其餘等級的日誌訊息會寫入標準輸出。
WriteLogger
的基本用法如下:
use std::fs::File;
fn main() {
simplelog::WriteLogger::init(simplelog::LevelFilter::Info, simplelog::Config::default(), File::create("app.log").unwrap()).unwrap();
...
}
以上用法,會將日誌訊息寫進目前工作目錄的app.log
檔案中。
CombinedLogger
的基本用法如下:
use std::fs::File;
fn main() {
simplelog::CombinedLogger::init(vec![
simplelog::SimpleLogger::new(simplelog::LevelFilter::Info, simplelog::Config::default()),
simplelog::WriteLogger::new(simplelog::LevelFilter::Info, simplelog::Config::default(), File::create("app.log").unwrap()),
])
.unwrap();
...
}
以上用法,會將日誌訊息寫進目前工作目錄的app.log
檔案,以及標準輸出和標準錯誤中。
若要修改日誌訊息的格式,就不能直接使用simplelog::Config
結構體,而是要透過simplelog::ConfigBuilder
結構體來產生simplelog::Config
結構實體。如下:
fn main() {
simplelog::SimpleLogger::init(
simplelog::LevelFilter::Info,
simplelog::ConfigBuilder::new().set_time_format_str("%+").set_time_to_local(true).build(),
)
.unwrap();
...
}
simplelog
不能完全自訂訊息的格式,只能按照ConfigBuilder
結構實體提供的現有方法來設定。
syslog
syslog
是Unik-like的作業系統用來統一管理系統日誌所用的協議或者說服務。syslog
這個crate可以讓log
的日誌訊息輸出給syslog
進行管理。
基本用法如下:
fn main() {
let formatter = syslog::Formatter3164 {
facility: syslog::Facility::LOG_USER,
hostname: None,
process: String::from("myprogram"),
pid: unsafe { libc::getpid() },
};
let logger = syslog::unix(formatter).expect("could not connect to syslog");
log::set_boxed_logger(Box::new(syslog::BasicLogger::new(logger))).map(|_| log::set_max_level(log::LevelFilter::Info)).expect("could not register logger");
...
}
console_log
console_log
可以在Webassembly程式中將日誌訊息輸出到主控台(console)。
基本用法如下:
fn main() {
console_log::init_with_level(log::Level::Info).unwrap();
...
}