Always had the problem that if I wanted to just log an error, rather than bubble it all the way up to main(), that you wouldn’t get a stacktrace. You could iterate the source chain and plug the stacktrace together yourself, but it’s rather complex code.

Now I realized, you can do this to get a stacktrace:

let error = todo!("Get an error somehow...");
let error = anyhow::anyhow!(error); //converts to an `anyhow::Error`
eprintln!("Error with stacktrace: {error:?}");

For converting to an anyhow::Error, it often also makes sense to use anyhow::Context like so:

use anyhow::Context;
let error = error.context("Deleting file failed.");
  • copacetic@discuss.tchncs.de
    link
    fedilink
    English
    arrow-up
    2
    ·
    51 minutes ago

    Since context is kind of on topic, what should one write there? Are there any principles for writing good context messages?

    fn beebboop() {
        foo();
        bar().context("frobnicating");
        baz();
    }
    

    Instead of “frobnicating” in this rough example, I could also write that we intend to “baz” afterwards or that we are “currently beebbooping” or “bar failed” or I could even mention “foo” or …

    From my (rather limited) experience, it seems most useful to describe what beebboop is about to do. Sometimes that is weird though, because then the context for foo, bar, and baz calls would be essentially the same.

    • TehPers@beehaw.org
      link
      fedilink
      English
      arrow-up
      1
      ·
      edit-2
      52 seconds ago

      Adding context does two things:

      • Add additional messages to the error for debugging purposes
      • Attach data to the error for the callers

      For the former, I prefer all lowercase, very concise, and with the intent to describe what beebboop was doing when it failed (in your example). More practically, what produced the error could be writing to the filesystem, and the additional context could be that the filesystem was being written to because it was saving a config file.

      For the latter, this is actually a lot different. anyhow lets you downcast its errors into any of its contexts. You can use this to create aggregated errors, test for specific contexts, or attach useful details to the error which might help with recovering from it.

    • Ephera@lemmy.mlOP
      link
      fedilink
      English
      arrow-up
      1
      ·
      3 hours ago

      Ah yeah, I was thinking of the “Caused by” shenanigans, which it prints in addition to the stacktrace.

  • Ephera@lemmy.mlOP
    link
    fedilink
    English
    arrow-up
    3
    ·
    14 hours ago

    Other useful Debug implementations:

    • PathBuf and Path will give you the path with quotes and I believe, it escapes any non-UTF8 characters.
    • std::time::Duration will give you reasonably formatted output, like Duration::from_millis(54321) will format as 54.321s, whereas Duration::from_millis(321) will format as 321ms. Not appropriate for every situation, but works pretty well for logging.
  • TehPers@beehaw.org
    link
    fedilink
    English
    arrow-up
    2
    ·
    14 hours ago

    The error returns a stack trace like this because if you return Err(anyhow::Error) from your main function, it debug prints the error when it exits.

    As you mentioned, it’s very convenient during logging! The Display impl shows much less information, so usually you want the Debug info of anyhow::Error during logging.