hiro99ma blog

rust: Err(e)で関数名を出力したい

anyhow::Result<T> でいろいろと楽はできるものの、どの関数を通っていったかはそのままではわからない。
C言語でいうところの __func__(C99) みたいなやつがしたい。

そのままやってみよう

use anyhow::{Context, Result};

fn abc() -> Result<()> {
    anyhow::bail!("これはabc error")
}

fn abc_normal() -> Result<()> {
    abc()
}

fn abc_context() -> Result<()> {
    abc().context("私はabc_context")
}

fn abc_with_context() -> Result<()> {
    abc().with_context(|| "私はabc_with_context")
}

fn main() {
    match abc_normal() {
        Ok(_) => {},
        Err(e) => eprintln!("その1: {:?}", e),
    };
    match abc_context() {
        Ok(_) => {},
        Err(e) => eprintln!("その2: {:?}", e),
    };
    match abc_with_context() {
        Ok(_) => {},
        Err(e) => eprintln!("その3: {:?}", e),
    };
}

実行するとこうなった。
.context() などを付けないと直近の情報しか出てこない。

その1: これはabc error
その2: 私はabc_context

Caused by:
    これはabc error
その3: 私はabc_with_context

Caused by:
    これはabc error

RUST_BACKTRACE=1 で実行すると .rustup/ にあるログまで出したりする。 これだとちょっと多過ぎな感じがした。

eprintln! の中身を {} だけにするとこうなる。 一番最後の情報だけ出力されるのかな。

その1: これはabc error
その2: 私はabc_context
その3: 私はabc_with_context

{:?} から {:#} に変更するとトレースとしてではなく付加情報として元の情報も出力する。
これも .context() などを付けたところだけしか出力されない。

その1: これはabc error
その2: 私はabc_context: これはabc error
その3: 私はabc_with_context: これはabc error

いくつかマクロはある

情報をとってくるマクロはいくつかあるそうだ。

関数名はとれないのか。

use anyhow::{Context, Result};

fn abc() -> Result<()> {
    anyhow::bail!("これはabc error: {}:{}:{}", std::file!(), std::line!(), std::module_path!())
}

fn abc_with_context() -> Result<()> {
    abc().with_context(|| format!("私はabc_with_context: {}:{}:{}", std::file!(), std::line!(), std::module_path!()))
}

fn main() {
    match abc_with_context() {
        Ok(_) => {},
        Err(e) => eprintln!("その3: {:?}", e),
    };
}

実行するとこうなる。

その3: 私はabc_with_context: src/main.rs:8:hello

Caused by:
    これはabc error: src/main.rs:4:hello

関数名がとれないと、ファイルを変更した後でログを見返しても対応が取れないのよね。
まあ、ログが大切なシーンであればソース管理もやるだろうからなんとかはなると思うけど、 ちょっとした開発中とかだと、ね。

tracing_subscriber

libp2pのping example でログを出力するライブラリが使われていた。
tracing_subscriber というらしい。 お前も tokio-rs の仲間か!

一番簡単なログだけでも、と思ったが既にめんどくさそう。。。
ログを構造化したり JSON にしたりと多彩なところがよいところなので、簡易なログであれば別のライブラリを使うのがよいだろう。

デフォルトで使うだけならそれほど準備はいらないようだ。
ネットで検索すると非同期の時には気を付けることがあるというのが出てきたが、 Spans のところしか async/await が出てこなかったので使わないうちは大丈夫なのかな? 暗黙で使われているとかだと困るのだが、まあまだいいや。

use anyhow::{Context, Result};
use tracing::{error, info, trace};
use tracing_subscriber;

fn abc() -> Result<()> {
    error!("エラー");
    anyhow::bail!("これはabc error: {}:{}:{}", std::file!(), std::line!(), std::module_path!())
}

fn abc_with_context() -> Result<()> {
    abc().with_context(|| format!("私はabc_with_context: {}:{}:{}", std::file!(), std::line!(), std::module_path!()))
}

fn main() {
    tracing_subscriber::fmt::init();

    info!("main関数");

    trace!("abc_with_contextを実行");
    match abc_with_context() {
        Ok(_) => { trace!("OKでした") },
        Err(e) => error!("エラーでした: {:?}", e),
    };
}

RUST_LOG の設定で出力の制御ができる。
日時とモジュール名までは出力してくれそうだ。

~/rust/hello$ ./target/debug/hello 
2025-11-30T02:37:30.671999Z  INFO hello: main関数
2025-11-30T02:37:30.672067Z ERROR hello: エラー
2025-11-30T02:37:30.672084Z ERROR hello: エラーでした: 私はabc_with_context: src/main.rs:11:hello

Caused by:
    これはabc error: src/main.rs:7:hello


~/rust/hello$ RUST_LOG=trace ./target/debug/hello 
2025-11-30T02:37:39.944203Z  INFO hello: main関数
2025-11-30T02:37:39.944246Z TRACE hello: abc_with_contextを実行
2025-11-30T02:37:39.944254Z ERROR hello: エラー
2025-11-30T02:37:39.944264Z ERROR hello: エラーでした: 私はabc_with_context: src/main.rs:11:hello

Caused by:
    これはabc error: src/main.rs:7:hello

モジュール名は出力してくれるので、ファイル名などもできるのだと思う。
やり方が分からんかったがこちらに載っていた。
他にも .with_~() でカスタマイズできる。残念ながら関数名はなかった。

use anyhow::{Context, Result};
use tracing::{error, info, trace};
use tracing_subscriber;

fn abc() -> Result<()> {
    error!("エラー");
    anyhow::bail!("これはabc error: {}:{}:{}", std::file!(), std::line!(), std::module_path!())
}

fn abc_with_context() -> Result<()> {
    abc().with_context(|| format!("私はabc_with_context: {}:{}:{}", std::file!(), std::line!(), std::module_path!()))
}

fn main() {
    tracing_subscriber::fmt()
        .with_file(true)
        .with_line_number(true)
        .without_time()
        .init();

    info!("main関数");

    trace!("abc_with_contextを実行");
    match abc_with_context() {
        Ok(_) => { trace!("OKでした") },
        Err(e) => error!("エラーでした: {:?}", e),
    };
}

こうなる。
タグジャンプできる形式なのでありがたい。

$ RUST_LOG=trace ./target/debug/hello 
 INFO hello: src/main.rs:21: main関数
ERROR hello: src/main.rs:6: エラー
ERROR hello: src/main.rs:26: エラーでした: 私はabc_with_context: src/main.rs:11:hello

Caused by:
    これはabc error: src/main.rs:7:hello

panic!の方が楽ではある

このしくみはログ出力であって Err に履歴を追加するような目的では使えない。 エラーを起こしている根っこの部分がわかるのであればそこで panic! させてトレースログを出した方が楽ではある。
なんたってファイル名や行番号だけでなく関数名も出てくるしね。

なのでデバッグ中はわざと .unwrap() とか .expect() したいという気持ちもある。
ただそうするとデバッグが終わった後に元に戻す作業が面倒だ。
それに、Err.context() などで情報を埋め込むのも面倒だし、結局それは人間が確認するための出力になる。 .context() で埋め込む利点は、ログだけだと階層というかエラーによるトレースで出力されたのかどうかが分かりづらい点くらいかな?

anyhow::Contextは簡易ログ?

となると、.context() は人間が見るための簡易ログと思っていて良いのだろうか。
別のところで使っていてJSON のエラーにしているのだが、\n などが入っていて見づらいのだ。
それならもう .context() は直近のエラーだけにしてしまうというのもありか。
まあ、実装に関係ないユーザがそういうエラーをもらっても「エラーが起きました」以上のことはわからんかもしれんがね。

writer: hiro99ma
tags: Rust言語

 
About me
About me
comment
Comment Form
🏠
Top page
GitHub
GitHub
Twitter
X/Twitter
My page
Homepage