Logging in Rust Applications: Best Practices for Beginners
\nLogging is one of the most important aspects of developing any application. It helps track errors, analyze performance, and understand system behavior in production. The Rust ecosystem has several powerful libraries for logging that allow flexible configuration of message output. In this article, we will cover the best practices for logging in Rust for beginners, starting from basic concepts to real code examples.
\n\nWhy is Logging Important in Rust?
\nRust is a systems programming language often used for building high-load and mission-critical applications. In such systems, every error can lead to serious consequences. Logging allows you to:
\n- \n
- Catch errors — even if the application doesn't crash, logs help understand what went wrong. \n
- Monitor performance — measure function execution times and find bottlenecks. \n
- Debug complex scenarios — in multithreaded applications, logs are indispensable for understanding execution order. \n
- Ensure transparency — in production, you can see what is happening with the application in real time. \n
Rust's standard library does not include a built-in logger, but the community has created excellent crates (libraries) for this task. We will look at two of the most popular: log + env_logger and tracing.
Getting Started with the log and env_logger Crates
\nThe simplest way to add logging to a Rust application is to use the combination of the log and env_logger crates. log provides abstract macros for logging, while env_logger provides a concrete implementation that is configured via environment variables.
Add dependencies to Cargo.toml:
[dependencies]\nlog = "0.4"\nenv_logger = "0.10"\nNow a simple example:
\nuse log::{info, warn, error, debug};\n\nfn main() {\n env_logger::init();\n\n info!("Application started");\n debug!("Debug mode active");\n warn!("This is a warning");\n error!("An error occurred: {}", "unknown code");\n}\nBy default, env_logger only outputs messages of level error and above. To see all levels, set the RUST_LOG environment variable:
RUST_LOG=debug cargo run\nYou will see messages of all levels: error, warn, info, debug, and trace. You can also filter by modules:
RUST_LOG=my_crate=info cargo run\nThis will output only info and above for the my_crate crate.
Advanced Logging with tracing
\nFor more complex applications (web servers, asynchronous systems), it is recommended to use the tracing crate. It supports structured logging, spans, and events, which is especially useful for tracking call chains in asynchronous code.
Add dependencies:
\n[dependencies]\ntracing = "0.1"\ntracing-subscriber = "0.3"\nExample using spans:
\nuse tracing::{info, error, span, Level};\nuse tracing_subscriber;\n\nfn main() {\n tracing_subscriber::fmt::init();\n\n let outer_span = span!(Level::INFO, "outer", work_id = 42);\n let _enter = outer_span.enter();\n\n info!("Starting work");\n process_data();\n info!("Work completed");\n}\n\nfn process_data() {\n let inner_span = span!(Level::DEBUG, "process_data", data_id = 100);\n let _enter = inner_span.enter();\n\n // Simulating an error\n if true {\n error!("Data processing error: ID {}", 100);\n }\n}