slog-jsonで構造化したJSONログを出力する

slog-bunyanを参考にslog-jsonを使ってJSONフォーマットで構造化されたログ出力を試してみました。
2021.03.30

この記事は公開されてから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を使って独自フォーマットの構造化されたログが出力できました。