Rust の slog の基本設定
開発において効果的なログ管理は非常に重要です。
slog
は構造化ログのための強力なライブラリなのですが、意外とまとまった記事が見つかりませんでした。もちろん公式サイトにはちゃんと書いてあるのですが、基本的な内容がほしいなと思いまとめてみました。
全体像 (logging.rs)
最初に、DEFAULT
としてslog
を設定している具体例を示し、順に解説していきます。
この例ではバージョン情報が全体のログに付与されるように設定してあります。
use once_cell::sync::Lazy;
pub use slog::*;
use slog_async::Async;
pub static DEFAULT: Lazy<Logger> = Lazy::new(|| {
let mk_term = || {
slog_term::FullFormat::new(slog_term::TermDecorator::new().build())
.build()
.fuse()
};
let mk_json = || slog_json::Json::default(std::io::stdout()).fuse();
let format = std::env::var("RUST_LOG_FORMAT").unwrap_or_default();
let drain = match format.as_str() {
"json" => Async::default(slog_envlogger::new(mk_json())).fuse(),
_ => Async::default(slog_envlogger::new(mk_term())).fuse(),
};
Logger::root(
drain,
o!(
"version" => env!("CARGO_PKG_VERSION"),
),
)
});
[dependencies]
slog = { version = "2.7.0", features = ["max_level_trace", "release_max_level_info"] }
slog-async = "2.8.0"
slog-term = "2.9.1"
slog-json = "2.6.1"
slog-envlogger = "2.2.0"
slog::* を再エキスポート
賛否が分かれるかもしれませんが、、、
pub use slog::*;
これによってlogging
をインポートするだけでslog
のマクロにアクセスできるようになります。
環境変数でログレベルを設定
slog_envlogger
を使うと環境変数RUST_LOG
で柔軟にログレベルを変更することが可能です。
let drain = Async::default(slog_envlogger::new(mk_term())).fuse();
export RUST_LOG=debug
この設定により、debug
レベルまでのログが出力されるようになります。
最大ログレベルの制御
feature
で最大のログレベルを設定できます。例えば、以下のように設定することで、開発中は詳細なtrace
レベルのログを取得し、リリースビルドではinfo
レベルまでのログのみを出力することができます。これにより、不要なログ出力を抑え、実行時のパフォーマンスを向上させることができます。
(参考: Notable details)
slog = { version = "2.7.0", features = ["max_level_trace", "release_max_level_info"] }
注意点として、環境変数RUST_LOG
で設定したログレベルは、このfeature
で指定した最大ログレベルを超えることはできません。(エラーになるわけではなくfeature
での値が優先されます)
ログフォーマットの指定
独自のやり方ですが、ログのフォーマットも環境変数RUST_LOG_FORMAT
を用いて簡単に変更できるようにしてみました。
let format = std::env::var("RUST_LOG_FORMAT").unwrap_or_default();
let drain = match format.as_str() {
"json" => Async::default(slog_envlogger::new(mk_json())).fuse(),
_ => Async::default(slog_envlogger::new(mk_term())).fuse(),
};
デフォルトではターミナルフォーマットが使用され、運用環境やデバッグのニーズに応じて簡単に切り替えることができます。
(slog-env-cfg というのもあるのですが、メンテされてないっぽい)
使い方
次のように使います。
use logging::*;
let log = DEFAULT.new(o!("function" => "main"));
info!(log, "Starting up");
debug!(log, "log level check");
trace!(log, "log level check");
error!(log, "log level check");
warn!(log, "log level check");
crit!(log, "log level check");
構造化
以下のようにログに key=>value の値を付けることができます。
value に使う値はslog::Value
を実装していることが期待されていて、ほとんどの型はそのままでいけます。そうでない場合は%
を付けておけば OK です。
(参考: fmt::Display and fmt::Debug values)
let log = DEFAULT.new(o!(
"function" => "get_calculated_value",
"id" => self.id,
));
info!(log, "start");
let a = 1_u8;
let b = BigUint::from(2_u8);
let c = BigDecimal::from(3_u8);
debug!(log, "details";
"a" => a,
"b" => %b,
"c" => %c,
);
まとめ
slog
を利用することで、型セーフな構造化されたロギングができるようになります。Async
でマルチスレッド環境でも安全です。
構造化されたロギングの利点は、ログメッセージに加えて、関連するコンテキスト情報を一緒に記録することです。これによりログの分析や検索が容易になり、問題の特定やデバッグが迅速に行えるようになります。
例えば AWS の環境であれば、json で S3 に書き出しておけば Athena のクエリで key=>value の値で検索することが可能です。
Golang にも 1.21 から標準で入ってますし、皆さんもログをどんどん構造化していきましょう!
Discussion