Logging for Rust: Best Solutions
Logging is a critically important component of any serious Rust application. It helps track code execution, identify errors, monitor performance, and analyze system behavior in production. In this article, we will review the main libraries for logging in the Rust ecosystem, their features, advantages, and usage examples.
Why is Logging Important in Rust?
Rust, being a systems language, places high demands on reliability and predictability. Logging allows you to:
- Track execution flow in multithreaded applications.
- Record unexpected states and panics.
- Conduct security audits.
- Collect metrics for performance optimization.
Main Libraries for Logging
1. log — Logging Facade
log is the standard facade (abstract layer) for logging in Rust. It defines the macros info!, warn!, error!, debug!, trace! and the Log trait. By itself, it does not output logs — a backend (e.g., env_logger or slog) is required for that.
// Cargo.toml[dependencies]log = "0.4"
// main.rsuse log::{info, warn, error};
fn main() { // Backend initialization is mandatory env_logger::init(); info!("Application started"); warn!("Low memory: {}%", 15); error!("Critical error: {}", "connection failure");}2. env_logger — Simple and Fast Backend
env_logger is the most popular backend for log. It is configured via the RUST_LOG environment variable. Supports filtering by modules and levels.
// Installation// cargo add env_logger
// Configuration exampleuse log::LevelFilter;
fn main() { env_logger::Builder::from_env( env_logger::Env::default().default_filter_or("info") ).init();
log::info!("Running with INFO level"); log::debug!("This message will not be displayed");}
// Run with variable:// RUST_LOG=debug cargo run3. slog — Structured Logging
slog is a powerful library for structured logging. Supports key-value pairs, logger chains, asynchronous writing, and custom drivers. Ideal for complex systems where context is important.
// Cargo.toml[dependencies]slog = "2.7"slog-term = "2.9"slog-json = "2.6"
// main.rsuse slog::{o, Drain, Logger, info};
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 logger = Logger::root(drain, o!("version" => "1.0"));
info!(logger, "Server started"; "port" => 8080, "env" => "production");}4. tracing — Instrumentation and Tracing
tracing is a modern library from the Tokio team, designed for structured logging and tracing. It allows creating spans — time intervals with context, which is indispensable for asynchronous and multithreaded applications.
// Cargo.toml[dependencies]tracing = "0.1"tracing-subscriber = "0.3"
// main.rsuse tracing::{info, span, Level};use tracing_subscriber::fmt;
fn main() { // Initialize subscriber fmt::init();
let span = span!(Level::INFO, "main", "app" = "my_service"); let _guard = span.enter();
info!("Processing request"); // Logs inside the span automatically contain context}Library Comparison
- log + env_logger: Simplicity, minimalism, suitable for small projects