24 releases (4 breaking)

new 0.5.2 Jul 16, 2024
0.5.0 Jun 15, 2024
0.4.0 Jan 29, 2024
0.1.7 Dec 26, 2023
0.1.5 Jul 1, 2023

#135 in Debugging

Download history 160/week @ 2024-03-28 141/week @ 2024-04-04 97/week @ 2024-04-11 87/week @ 2024-04-18 85/week @ 2024-04-25 78/week @ 2024-05-02 103/week @ 2024-05-09 147/week @ 2024-05-16 157/week @ 2024-05-23 193/week @ 2024-05-30 125/week @ 2024-06-06 280/week @ 2024-06-13 165/week @ 2024-06-20 162/week @ 2024-06-27 194/week @ 2024-07-04 360/week @ 2024-07-11

891 downloads per month
Used in 28 crates (5 directly)

ISC license

32KB
762 lines

Errors and log messages are closely related - both are events, where logs are output in realtime (to a terminal or remote logger) without affecting execution flow and errors are passed up in lieu of a successful result for another context to either log or discard. Both provide information what was happening at the time of the event, including context up the stack (what was happening, as part of what larger actions, and with what relationships to other entities).

With this library you build a tree of Log objects to store context at multiple levels and the Log object has methods for logging and creating errors which contain the full tree of context.

use loga::{
    ea,
    ResultContext,
    INFO,
};

fn main1() -> Result<(), loga::Error> {
    // All errors stacked from this will have "system = main"
    let log = &loga::Log::new_root(INFO).fork(ea!(system = "main"));

    // Convert the error result to `loga::Error`, add all the logger's attributes, add
    // a message, and add additional attributes.
    let res =
        http_req(
            "https://example.org",
        ).stack_context_with(log, "Example.org is down", ea!(process = "get_weather"))?;
    match launch_satellite(res.body) {
        Ok(_) => (),
        Err(e) => {
            let e = e.stack_context(log, "Failed to launch satellite");
            if let Err(e2) = shutdown_server() {
                // Attach incidental errors
                return Err(e.also(e2.into()));
            }
            return Err(e);
        },
    }
    if res.code == 295 {
        return Err(loga::err("Invalid response"));
    }
    log.log(INFO, "Obtained weather");
    return Ok(());
}

fn main() {
    match main1() {
        Ok(_) => (),
        Err(e) => loga::fatal(e),
    }
}

Goals

The goal of this crate is to make it easy to produce clear and informative log messages and errors with full context. To do that creating errors and log messages as well as adding context must be easy.

Optimization will be considered as necessary as far as it doesn't impact ease of use and expressivity.

Event structure

Events (errors and log messages) also have a tree structure, with the following dimensions:

  • Attributes at the current level of context
  • One or more errors that this error adds context to (causes)
  • One or more errors that occurred while trying to handle this error (incidental)

The errors are intended only for human consumption. Any information that may need to be handled programmatically should be in a non-error return.

Usage tips

In non-logging functions or objects that may be shared in multiple contexts, rather than receive a logger from the caller it may be simpler to start a new (blank) Log tree internally, or just use .context. The caller can later root the context using .stack_context or the logger's context will naturally be added in log.log_err.

Notes

Currently logging is written to stderr only. Adding more log destinations and formats in the future would be nice.

Dependencies

~4–13MB
~145K SLoC