Logging in Rust Applications: Best Practices for Configuration

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why logging is critical for Rust applications

Logging is not just about writing events to a file. In a production environment, high-quality logs allow you to quickly diagnose errors, track performance, and analyze system behavior. Rust, being a systems programming language, offers flexible and performant tools for this task. In this article, we will cover the best practices for logging in Rust: from choosing a crate to configuring structured logs and rotation.



1. Basics: the log and env_logger crates

The Rust ecosystem offers a modular approach. The log crate provides an abstraction (facade), while a separate crate, such as env_logger or pretty_env_logger, provides the concrete implementation (backend).



Installation and setup

Add the dependencies to Cargo.toml:

[dependencies]log = "0.4"env_logger = "0.10"


Basic example

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

fn main() { env_logger::init();

info!("Application started"); let user_id = 42; warn!("User {} not found in cache", user_id); error!("Database connection error: {:?}", "timeout");}


Important: By default, logs are not displayed until you set the RUST_LOG environment variable. Run the program like this:

RUST_LOG=info cargo run


Logging levels

The log crate supports five standard levels (from most to least priority):

  • error — critical errors requiring immediate attention.
  • warn — warnings about potential problems.
  • info — general information about execution progress.
  • debug — debugging information for developers.
  • trace — extremely detailed events (often used in loops).

Filtering by modules: RUST_LOG=my_crate=debug,other_crate=info.



2. Advanced logging: the tracing crate

For complex applications (web servers, microservices), it is recommended to use the tracing crate. It provides the concept of spans (time intervals) and structured logging with context.



Installing tracing

[dependencies]tracing = "0.1"tracing-subscriber = "0.3"


Example with spans

use tracing::{info, instrument, span, Level};use tracing_subscriber;

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

let parent_span = span!(Level::INFO, "request_processing", request_id = 123); let _guard = parent_span.enter(); process_data();}

#[instrument]fn process_data() { info!("Data processing started"); std::thread::sleep(std::time::Duration::from_millis(50)); info!("Data processing finished");}

The #[instrument] attribute automatically creates a span for the function and records execution time. The output will contain nested timestamps, which is invaluable for profiling.



3. Structured logs (JSON) and rotation

In a production environment, logs are often collected centrally (ELK, Loki, Grafana). For this purpose, it is convenient to output logs in JSON format.



JSON output with tracing-subscriber

use tracing_subscriber::fmt::format::Json;use tracing_subscriber::prelude::*;

fn main() { let format = Json::new() .with_timer(tracing_subscriber::fmt::time::UtcTime::rfc3339()) .with_target(true) .with_level(true);

tracing_subscriber::fmt() .event_format(format) .init();

tracing::info!("Server started on port 8080");}

Now the logs will look like this:

Blogs

Book Recommendations