この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
最近Rustを触っていてJSONフォーマットを出力できるロガーが必要だったので、slog-bunyan を参考にして出力フォーマットをカスタマイズしてみました。
方針
slog-bunyanと同様にslog-jsonを使ってログをフォーマットします。slog-bunyanとの差異は以下の通りです。
- name フィールドにモジュール名を設定する(slog-bunyanでは固定の値)
- timeフィールドにはエポックミリ秒を設定する(slog-bunyanではRFC3339フォーマット)
- levelフィールドの値はlogbackの同様の定数を使用する(INFO_INTなど)
実装
Cargo.tomlとソースコードは以下のようになります。ほぼslog_bunyanのコードそのままになりますが、いくつかのフィールドで値を変更しています。
Cargo.toml
[dependencies]
slog = {version = "2.7.0"}
slog-json = "2.3.0"
chrono = "0.4.19"
logger.rs
use slog::{Drain, FnValue, Logger, Record, info, o};
use std::sync::Mutex;
static LEVEL_VALUES: [u32; 7] =
[
u32::MAX, //OFF
50000, //CRITICAL
40000, //ERROR
30000, //WARN
20000, //INFO
10000, //DEBUG
5000, //TRACE
];
pub fn create_logger() -> Logger {
//JSONログメッセージを組み立て
let json = slog_json::Json::new(std::io::stdout())
.add_key_value(o!(
"pid" => ::std::process::id(),
"time" => FnValue(|_: &Record| chrono::Local::now().timestamp_millis()),
"level" => FnValue(|rinfo : &Record| LEVEL_VALUES[rinfo.level().as_usize()]),
"name" => FnValue(|rinfo: &Record| rinfo.module()),
"msg" => FnValue(|rinfo : &Record| {
rinfo.msg().to_string()
})
))
.build();
return slog::Logger::root(Mutex::new(json).fuse(), o!());
}
pub fn log_in_module(logger:&Logger){
info!(logger, "this is log from module");
}
main.rs
mod logger;
use slog::{info, warn, o};
fn main() {
let logger = logger::create_logger();
info!(logger, "this is info log");
let child = logger.new(o!("context_aware" => "some_value"));
warn!(child, "this is child warn log");
logger::log_in_module(&child);
}
出力
{"msg":"this is info log","name":"json_logger_rust","level":20000,"time":1617095591806,"pid":31832}
{"msg":"this is child warn log","name":"json_logger_rust","level":30000,"time":1617095591807,"pid":31832,"context_aware":"some_value"}
{"msg":"this is log from module","name":"json_logger_rust::logger","level":20000,"time":1617095591807,"pid":31832,"context_aware":"some_value"}
うまくいかなったバージョン
当初フィールドを上書きするために以下のようにslog_bunyanが生成するJsonBuilderにフィールドを追加していたのですが、出力されるJSONで追加したフィールドが重複していたためJsonBuilderを最初から生成する方針に変更しました。
let json = slog_bunyan::with_name("app", std::io::stdout())
.add_key_value(o!("name" => FnValue(|rinfo : &Record| rinfo.module())))
.build();
まとめ
slog-jsonを使って独自フォーマットの構造化されたログが出力できました。