Rust：logでログ出力を行う

この記事は[Wanoアドベントカレンダー](https://qiita.com/advent-calendar/2018/wano-group)の14日目の記事です。

## TL;DR

* [log](https://crates.io/crates/log) crateと[env_logger](https://crates.io/crates/env_logger) crateで基本的には何でも出来る
* `env_logger`では不十分な場合は[crate.io](https://crates.io/)で探すか、自分で[Log](https://docs.rs/log/0.4.6/log/trait.Log.html) traitを実装する

## Rustでログ出力をするにはlogとlogに対応したcrateを使おう

Rustにはログ出力の為の枠組みを定めた[log](https://crates.io/crates/log) crateというものがあります。
log crateは`Log` traitと`debug`/`info`/`warn`/`error`という`println`と同じ引数を取れるログ出力の為のマクロを提供していて、`debug`や`warn`マクロでログ出力を行っていれば、`Log` traitの実装として何を利用するかは自由に選べるようになっています。

この`Log` traitを実装したライブラリには次のような物があります。

* [env_logger](https://crates.io/crates/env_logger)
* [simplelog](https://crates.io/crates/simplelog)
* [log4rs](https://crates.io/crates/log4rs)
* [syslog](https://crates.io/crates/syslog)
* [slog-stdlog](https://crates.io/crates/slog-stdlog)

例えば`env_logger`を使う場合は次の様なコードになります。

~~~~rust
use env_logger;
use log::{error, warn, info, debug};
use std::env;

fn main() {
    env::set_var("RUST_LOG", "info");
    env_logger::init();
    
    debug!("debugです");
    info!("infoです");
    warn!("warnです");
    error!("errorです");
}
~~~~

env_loggerはその名の通り`RUST_LOG`環境変数によって出力するログレベル等を設定する事ができます。
上のコードでは`info`を値に入れていますので、infoレベル以上のログレベルのログだけ出力されます。つまりdebugログは出力されません。

またenv_loggerのようなcrateは多くが`init`関数を提供しています（恐らく`log` crateのドキュメントにこういう関数を提供して初期化処理を隠蔽しましょうと書いてある為）。
これによってロガーの初期化が行われますので、main関数の最初に呼ぶようにしましょう。

上のコードを実行した結果は次の様になります。

~~~~text
[2018-12-15T05:53:53Z INFO  playground] infoです
[2018-12-15T05:53:53Z WARN  playground] warnです
[2018-12-15T05:53:53Z ERROR playground] errorです
~~~~

## env_loggerの出力をカスタマイズする

env_loggerの標準のフォーマットでは不足がある場合はカスタマイズする事ができます。
ここではログ出力を行ったファイル名と行番号を足してみましょう。

env_loggerには出力フォーマットの設定をする為に[Builder](https://docs.rs/env_logger/0.6.0/env_logger/struct.Builder.html)型が提供されています。
この型の[format](https://docs.rs/env_logger/0.6.0/env_logger/struct.Builder.html#method.format)メソッドを使って出力内容をカスタムします。

~~~~rust
use env_logger;
use log::{error, warn, info, debug};
use std::env;
use std::io::Write;

fn main() {
    env::set_var("RUST_LOG", "info");
    env_logger::Builder::from_default_env()
        .format(|buf, record| {
            let ts = buf.timestamp();
            writeln!(
                buf,
                "[{} {} {}] {} {}:{}",
                ts,
                record.level(),
                record.target(),
                record.args(),
                record.file().unwrap_or("unknown"),
                record.line().unwrap_or(0),
            )
        })
        .init();
    
    debug!("debugです");
    info!("infoです");
    warn!("warnです");
    error!("errorです");
}
~~~~

`Builder::from_default_env()`でBuilderオブジェクトを生成する事で`init`で初期化した場合と使い勝手を変わらなくすることが出来ます（この他に`new`と`from_env`というメソッドでも`Builder`オブジェクトを生成できますが、こちらは挙動が少し変わります）。

次にBuilderの`format`メソッドを使ってログのフォーマットを定義します。
formatメソッドは`Fn(&mut Formatter, &Record) -> Result<()> + Sync + Send`という型のクロージャを引数に取ります。
あとは[Formatter](https://docs.rs/env_logger/0.6.0/env_logger/fmt/struct.Formatter.html)と[Record](https://docs.rs/log/0.4.6/log/struct.Record.html)のドキュメントを参照しつつ、`writeln`マクロで`Formatter`に書き込むコードを書きます。

このコードを実行すると次のような出力になります。

~~~~text
[2018-12-15T06:45:39Z INFO playground] infoです src/main.rs:25
[2018-12-15T06:45:39Z WARN playground] warnです src/main.rs:26
[2018-12-15T06:45:39Z ERROR playground] errorです src/main.rs:27
~~~~

各行の末尾にファイル名と行番号を追加することが出来ました。

## env_loggerでは不十分な場合

[crate.io](https://crates.io/)で必要な機能を持ったcrateを探すか、自分で[Log](https://docs.rs/log/0.4.6/log/trait.Log.html) traitを実装するかのどちらかになります。

`Log` traitを実装する時は次の3つのメソッドを実装します。

* [enabled](https://docs.rs/log/0.4.6/log/trait.Log.html#tymethod.enabled)
* [log](https://docs.rs/log/0.4.6/log/trait.Log.html#tymethod.log)
* [flush](https://docs.rs/log/0.4.6/log/trait.Log.html#tymethod.flush)

また初期化関数(例えば`init`関数)を提供し、その中で`set_logger`(又は`set_boxed_logger`)と`set_max_level`関数を呼んで自前のロガーとログレベルをセットします。