Logging Libraries

Libraries for application logging and diagnostics.

log

The standard logging facade for Rust.

[dependencies]
log = "0.4"

Basic Usage

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

fn process_data() {
    trace!("Entering process_data");
    debug!("Processing {} items", 42);
    info!("Processing complete");
    warn!("Disk space low");
    error!("Failed to write file: {}", err);
}

Log Levels

Level Purpose
error! Errors that prevent operation
warn! Warnings, recoverable issues
info! General information
debug! Debug information
trace! Fine-grained tracing

env_logger

Simple environment-based logger.

[dependencies]
env_logger = "0.11"
log = "0.4"
fn main() {
    env_logger::init();

    log::info!("Application started");
}
# Set log level via environment
RUST_LOG=info ./myapp
RUST_LOG=debug ./myapp
RUST_LOG=mymodule=trace ./myapp
RUST_LOG=myapp=debug,other_crate=warn ./myapp

Custom Format

use env_logger::Builder;
use std::io::Write;

Builder::new()
    .format(|buf, record| {
        writeln!(
            buf,
            "{} [{}] {}",
            chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
            record.level(),
            record.args()
        )
    })
    .filter_level(log::LevelFilter::Info)
    .init();

tracing

Modern, async-aware diagnostics framework.

[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

Basic Usage

use tracing::{info, debug, warn, error, span, Level};

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

    info!("Application started");

    let span = span!(Level::INFO, "processing", items = 42);
    let _guard = span.enter();

    debug!("Processing items");
}

Spans

use tracing::{info_span, instrument};

#[instrument]
fn process_request(request_id: u64) {
    // Automatically creates span with function name and args
    info!("Processing request");
    do_work();
}

fn manual_span() {
    let span = info_span!("my_operation", key = "value");
    let _guard = span.enter();

    // All logs here include span context
    info!("Inside span");
}

Async Support

use tracing::instrument;

#[instrument]
async fn async_operation() {
    // Span persists across await points
    do_async_work().await;
    info!("Work complete");
}

Subscribers

use tracing_subscriber::{fmt, prelude::*, EnvFilter};

fn setup_tracing() {
    tracing_subscriber::registry()
        .with(fmt::layer())
        .with(EnvFilter::from_default_env())
        .init();
}

tracing-subscriber

Configurable tracing subscriber.

[dependencies]
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }

JSON Output

use tracing_subscriber::fmt;

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

    tracing::info!(user = "alice", action = "login", "User logged in");
}

Multiple Outputs

use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};

tracing_subscriber::registry()
    .with(fmt::layer().with_writer(std::io::stdout))
    .with(fmt::layer().json().with_writer(file))
    .init();

tracing-appender

Non-blocking file logging.

[dependencies]
tracing-appender = "0.2"
use tracing_appender::rolling::{RollingFileAppender, Rotation};

let file_appender = RollingFileAppender::new(
    Rotation::DAILY,
    "/var/log/myapp",
    "app.log",
);

let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);

tracing_subscriber::fmt()
    .with_writer(non_blocking)
    .init();

// Keep _guard alive for the duration of the program

fern

Flexible logging configuration.

[dependencies]
fern = "0.6"
log = "0.4"
chrono = "0.4"
use fern::Dispatch;

fn setup_logger() -> Result<(), fern::InitError> {
    Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "[{} {} {}] {}",
                chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
                record.level(),
                record.target(),
                message
            ))
        })
        .level(log::LevelFilter::Info)
        .level_for("noisy_crate", log::LevelFilter::Warn)
        .chain(std::io::stdout())
        .chain(fern::log_file("output.log")?)
        .apply()?;
    Ok(())
}

slog

Structured logging.

[dependencies]
slog = "2"
slog-term = "2"
slog-async = "2"
use slog::{o, info, Logger, Drain};

fn main() {
    let decorator = slog_term::TermDecorator::new().build();
    let drain = slog_term::FullFormat::new(decorator).build().fuse();
    let drain = slog_async::Async::new(drain).build().fuse();
    let log = Logger::root(drain, o!("version" => "1.0"));

    info!(log, "Application started"; "port" => 8080);
}

Comparison

Crate Style Async Structured
log + env_logger Simple No No
tracing Modern Yes Yes
fern Flexible No No
slog Structured Yes Yes

Best Practices

Library Code

// In library code, use log or tracing macros
// Let the application choose the implementation

use log::debug;

pub fn library_function() {
    debug!("Library debug message");
}

Application Code

// In application code, set up the subscriber/logger

fn main() {
    // Choose one logging implementation
    tracing_subscriber::fmt::init();

    // Or for simpler needs
    env_logger::init();
}

Structured Fields

use tracing::{info, instrument};

#[instrument(skip(password))]
fn login(username: &str, password: &str) -> Result<(), Error> {
    info!(username, "Login attempt");
    // password is not logged due to skip
    Ok(())
}

Summary

Crate Use Case
log Logging facade
env_logger Simple logging
tracing Modern diagnostics
tracing-subscriber Tracing configuration
tracing-appender File logging
fern Flexible log routing
slog Structured logging

Choosing a Solution

Need Recommendation
Simple logging log + env_logger
Async applications tracing
Structured logs tracing or slog
File rotation tracing-appender
Complex routing fern

Back to top

Rust Programming Guide is not affiliated with the Rust Foundation. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.