slog-jsonで構造化したJSONログを出力する
slog-bunyanを参考にslog-jsonを使ってJSONフォーマットで構造化されたログ出力を試してみました。
はじめに
最近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を使って独自フォーマットの構造化されたログが出力できました。