Crate fern

source
Expand description

Efficient, configurable logging in Rust.

§fern 0.4.4, 0.5.*, 0.6.* security warning - colored feature + global allocator

One of our downstream dependencies, atty, through colored, has an unsoundness issue: https://rustsec.org/advisories/RUSTSEC-2021-0145.html.

This shows up in one situation: if you’re using colored 0.1.0 (the crate, or our feature), and a custom global allocator.

Upgrade to fern 0.7.0, and colored 0.2.0 if you depend on it directly, to fix this issue.

§Depending on fern

Ensure you require both fern and log in your project’s Cargo.toml:

[dependencies]
log = "0.4"
fern = "0.7"

§Example setup

With fern, all logger configuration is done via builder-like methods on instances of the Dispatch structure.

Here’s an example logger which formats messages, and sends everything Debug and above to both stdout and an output.log file:

use log::{debug, error, info, trace, warn};
use std::time::SystemTime;

fn setup_logger() -> Result<(), fern::InitError> {
    fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "[{} {} {}] {}",
                humantime::format_rfc3339_seconds(SystemTime::now()),
                record.level(),
                record.target(),
                message
            ))
        })
        .level(log::LevelFilter::Debug)
        .chain(std::io::stdout())
        .chain(fern::log_file("output.log")?)
        .apply()?;
    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    setup_logger()?;

    info!("Hello, world!");
    warn!("Warning!");
    debug!("Now exiting.");

    Ok(())
}

Let’s unwrap this:

fern::Dispatch::new()

Dispatch::new creates an empty configuration.

.format(|out, message, record| {
    out.finish(format_args!(
        "..."
    ))
})

This incantation sets the Dispatch format! The closure taking in out, message, record will be called once for each message going through the dispatch, and the formatted log message will be used for any downstream consumers.

Do any work you want in this closure, and then call out.finish at the end. The callback-style result passing with out.finish(format_args!()) lets us format without any intermediate string allocation.

format_args! has the same format as println!, just returning a not-yet-written result we can use internally.

std::time::SystemTime::now()

std::time::SystemTime::now retrieves the current time.

humantime::format_rfc3339_seconds(
    // ...
)

humantime::format_rfc3339_seconds formats the current time into an RFC3339 timestamp, with second-precision.

RFC3339 looks like 2018-02-14T00:28:07Z, always using UTC, ignoring system timezone.

humantime is a nice light dependency, but only offers this one format. For more custom time formatting, I recommend chrono or time.

Now, back to the Dispatch methods:

.level(log::LevelFilter::Debug)

Sets the minimum logging level for all modules, if not overwritten with Dispatch::level_for, to Level::Debug.

.chain(std::io::stdout())

Adds a child to the logger. With this, all messages which pass the filters will be sent to stdout.

Dispatch::chain accepts Stdout, Stderr, File and other Dispatch instances.

.chain(fern::log_file("output.log")?)

Adds a second child sending messages to the file “output.log”.

See log_file.

// ...
.apply()

Consumes the configuration and instantiates it as the current runtime global logger.

This will fail if and only if .apply() or equivalent form another crate has already been used this runtime.

Since the binary crate is the only one ever setting up logging, and it’s usually done near the start of main, the Dispatch::apply result can be reasonably unwrapped: it’s a bug if any crate is calling this method more than once.


The final output will look like:

[2023-03-18T20:12:50Z INFO cmd_program] Hello, world!
[2023-03-18T20:12:50Z WARN cmd_program] Warning!
[2023-03-18T20:12:50Z DEBUG cmd_program] Now exiting.

§Logging

Once the logger has been set, it will pick up all logging calls from your crate and all libraries you depend on.


fern::Dispatch::new()
    // ...
    .apply()?;

trace!("Trace message");
debug!("Debug message");
info!("Info message");
warn!("Warning message");
error!("Error message");

§More

The Dispatch documentation has example usages of each method, and the full example program might be useful for using fern in a larger application context.

See the colors module for examples using ANSI terminal coloring.

See the syslog module for examples outputting to the unix syslog, or the syslog full example program for a more realistic sample.

See the meta module for information on getting logging-within-logging working correctly.

Modules§

  • Support for ANSI terminal colors via the colored crate.
  • Fern supports logging most things by default, except for one kind of struct: structs which make log calls to the global logger from within their Display or Debug implementations.
  • Example usage of fern with the syslog crate.

Structs§

  • This is used to generate log file suffixed based on date, hour, and minute.
  • The base dispatch logger.
  • Callback struct for use within a formatter closure
  • Configuration for a logger output.
  • Logger which will panic whenever anything is logged. The panic will be exactly the message of the log.

Enums§

  • Convenience error combining possible errors which could occur while initializing logging.

Functions§

  • Convenience method for opening a log file with common options.
  • Convenience method for opening a re-openable log file with common options.
  • Convenience method for opening a re-openable log file with common options.

Type Aliases§

  • A type alias for a log filter. Returning true means the record should succeed - false means it should fail.
  • A type alias for a log formatter.