env_logger
Most downloaded Rust logger implementation (41 million downloads). Characterized by minimal configuration via environment variables, controls log level with RUST_LOG environment variable. Adopted in many Rust projects due to simple and reliable operation.
Library
env_logger
Overview
env_logger is "a logging implementation for log which is configured via an environment variable," developed as the most downloaded logger implementation in the Rust ecosystem (41 million downloads). Conceptualized with "minimal configuration via environment variables," it controls log levels through the RUST_LOG environment variable with simple and reliable operation, leading to adoption in many Rust projects. Combined with the log crate facade, it provides the standard Rust logging experience and has established its position as the de facto standard used widely from beginners to advanced users.
Details
env_logger 2025 edition continues to maintain the highest market share as the de facto standard for Rust logging. Provides features optimized for Rust development including intuitive log level control via RUST_LOG environment variable, case-insensitive configuration, fine-grained log control per module, automatic output to standard error, and color-coded display support. Serves as learning starting point for new Rust developers as the first crate introduced in Rust Cookbook, establishing standard practice where combination with log crate is recommended in library development.
Key Features
- Environment Variable Control: Easy log control via RUST_LOG environment variable
- Most Downloaded: Highest download count among Rust logger implementations
- Module Support: Fine-grained control per crate and module
- Test Support: Duplicate initialization handling in test environments
- Customization: Detailed configuration via Builder pattern
- Standard Compliant: Standard implementation in Rust ecosystem
Pros and Cons
Pros
- Overwhelming prevalence and stability in Rust ecosystem
- Intuitive log control requiring no configuration via environment variables
- Simple design easy for beginners to understand
- Perfect integration with log crate and standard usage patterns
- Rich community support and learning resources
- Safe duplicate initialization handling in test environments
Cons
- Does not provide advanced logging features (structured logging, distributed tracing)
- May lack functionality for large-scale applications
- File output and rotation functionality at basic level
- Other choices prioritized in microservices environments
- Limited customization with few extension features
- No optimization specialized for asynchronous processing
Reference Pages
Usage Examples
Installation and Basic Setup
# Add dependencies for executable
cargo add log env_logger
# For test usage
cargo add log
cargo add --dev env_logger
# Example Cargo.toml entry
echo '[dependencies]' >> Cargo.toml
echo 'log = "0.4"' >> Cargo.toml
echo 'env_logger = "0.10"' >> Cargo.toml
# Version check
cargo tree | grep env_logger
Basic Log Output
use log::{info, warn, error, debug, trace};
fn main() {
// Initialize env_logger (once at program start)
env_logger::init();
// Basic log output
error!("Error message");
warn!("Warning message");
info!("Info message");
debug!("Debug message");
trace!("Detailed trace message");
// Data output
let user_id = 123;
let user_name = "John Doe";
info!("User processing: ID={}, Name={}", user_id, user_name);
// Struct output
#[derive(Debug)]
struct User {
id: u32,
name: String,
email: String,
}
let user = User {
id: 1,
name: "Jane Smith".to_string(),
email: "[email protected]".to_string(),
};
debug!("User details: {:?}", user);
// Conditional logging
let is_debug_mode = true;
if is_debug_mode {
debug!("Debug mode is enabled");
}
info!("Application startup completed");
}
// Execution examples
// RUST_LOG=info cargo run
// RUST_LOG=debug cargo run
Log Control via Environment Variables
# Basic log level settings
RUST_LOG=error cargo run # Errors only
RUST_LOG=warn cargo run # Warnings and above
RUST_LOG=info cargo run # Info and above
RUST_LOG=debug cargo run # Debug and above
RUST_LOG=trace cargo run # All logs
# Case-insensitive support
RUST_LOG=INFO cargo run # Same as info
RUST_LOG=Debug cargo run # Same as debug
# Multiple module control
RUST_LOG=myapp=info,tokio=warn cargo run
# Crate-level control
RUST_LOG=myapp=debug cargo run
# Module hierarchy control
RUST_LOG=myapp::database=trace,myapp::auth=info cargo run
# Exclusion patterns
RUST_LOG=debug,tokio=off cargo run
# Enable all
RUST_LOG=trace cargo run
# Specific crate only
RUST_LOG=myapp cargo run # Default level
Module-based Log Management
// main.rs
use log::{info, error};
mod database;
mod auth;
mod api;
fn main() {
env_logger::init();
info!("Application initialization started");
// Execute functions from each module
database::connect();
auth::login("[email protected]", "password");
api::start_server();
info!("Application startup completed");
}
// database.rs
use log::{info, debug, error};
pub fn connect() {
debug!("Database connection started");
match establish_connection() {
Ok(_) => {
info!("Database connection successful");
}
Err(e) => {
error!("Database connection failed: {}", e);
}
}
}
fn establish_connection() -> Result<(), String> {
// Connection processing simulation
debug!("Checking connection parameters...");
std::thread::sleep(std::time::Duration::from_millis(100));
// Success case
Ok(())
}
pub fn execute_query(sql: &str) -> Result<Vec<String>, String> {
debug!("SQL execution: {}", sql);
// Query execution simulation
let results = vec!["Result 1".to_string(), "Result 2".to_string()];
debug!("Query results: {} rows", results.len());
Ok(results)
}
// auth.rs
use log::{info, warn, error, debug};
pub fn login(email: &str, password: &str) -> Result<String, String> {
info!("Login attempt: {}", email);
// Password validation (use encrypted passwords in actual implementation)
if validate_credentials(email, password) {
let session_token = generate_session_token();
info!("Login successful: {}", email);
debug!("Session token generated: {}", &session_token[0..8]);
Ok(session_token)
} else {
warn!("Login failed: Invalid credentials - {}", email);
Err("Invalid credentials".to_string())
}
}
fn validate_credentials(email: &str, password: &str) -> bool {
debug!("Validating credentials: {}", email);
// Actual validation logic
email.contains("@") && password.len() >= 8
}
fn generate_session_token() -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
std::time::SystemTime::now().hash(&mut hasher);
format!("session_{:x}", hasher.finish())
}
// api.rs
use log::{info, debug, error};
pub fn start_server() {
info!("API server starting");
let port = 8080;
debug!("Listening on port {}", port);
// Server startup simulation
match bind_server(port) {
Ok(_) => {
info!("API server startup completed: http://localhost:{}", port);
}
Err(e) => {
error!("Server startup failed: {}", e);
}
}
}
fn bind_server(port: u16) -> Result<(), String> {
debug!("Attempting to bind port {}", port);
// Bind processing simulation
Ok(())
}
// Execution examples and output control
// RUST_LOG=myapp=info cargo run # Info level and above for all modules
// RUST_LOG=myapp::database=debug cargo run # Debug for database module
// RUST_LOG=myapp::auth=trace,myapp::api=info cargo run # Individual control
Test Environment Configuration
// lib.rs or main.rs
use log::info;
pub fn add_one(num: i32) -> i32 {
info!("add_one called with {}", num);
num + 1
}
pub fn calculate_sum(numbers: &[i32]) -> i32 {
info!("calculating sum of {} numbers", numbers.len());
let sum = numbers.iter().sum();
info!("sum calculated: {}", sum);
sum
}
#[cfg(test)]
mod tests {
use super::*;
use log::info;
// Test initialization function
fn init() {
// Use try_init() to prevent duplicate initialization
let _ = env_logger::builder().is_test(true).try_init();
}
#[test]
fn test_add_one() {
init();
info!("Starting add_one test");
let result = add_one(2);
assert_eq!(result, 3);
info!("add_one test completed successfully");
}
#[test]
fn test_add_one_negative() {
init();
info!("Starting negative number test");
let result = add_one(-5);
assert_eq!(result, -4);
info!("negative number test completed");
}
#[test]
fn test_calculate_sum() {
init();
info!("Starting sum calculation test");
let numbers = vec![1, 2, 3, 4, 5];
let result = calculate_sum(&numbers);
assert_eq!(result, 15);
info!("sum calculation test completed");
}
#[test]
fn test_empty_sum() {
init();
info!("Testing empty array sum");
let numbers = vec![];
let result = calculate_sum(&numbers);
assert_eq!(result, 0);
info!("empty array test completed");
}
}
// Test execution examples
// RUST_LOG=info cargo test
// RUST_LOG=mylib=debug cargo test test_add_one
// RUST_LOG=trace cargo test -- --nocapture
Customization via Builder Pattern
use env_logger::{Builder, Target, WriteStyle};
use log::LevelFilter;
use std::io::Write;
fn main() {
// Create custom builder
let mut builder = Builder::from_default_env();
// Configure output to stdout
builder.target(Target::Stdout);
// Set log level
builder.filter_level(LevelFilter::Info);
// Set custom format
builder.format(|buf, record| {
writeln!(
buf,
"{} [{}] - {}",
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.args()
)
});
// Initialize
builder.init();
// Test log output
log::error!("Error message");
log::warn!("Warning message");
log::info!("Info message");
log::debug!("Debug message"); // Not displayed due to filter_level(Info)
}
// More detailed customization example
fn setup_detailed_logger() {
use std::env;
let mut builder = Builder::new();
// Process environment variables
if let Ok(rust_log) = env::var("RUST_LOG") {
builder.parse_filters(&rust_log);
} else {
// Default log level setting
builder.filter_level(LevelFilter::Info);
}
// Color output configuration
builder.write_style(WriteStyle::Always);
// Timestamped format
builder.format(|buf, record| {
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
writeln!(
buf,
"[{}] {} [{}:{}] {}",
timestamp,
record.level(),
record.file().unwrap_or("unknown"),
record.line().unwrap_or(0),
record.args()
)
});
// Module-specific filters
builder
.filter_module("reqwest", LevelFilter::Warn)
.filter_module("hyper", LevelFilter::Error)
.filter_module("myapp::database", LevelFilter::Debug);
builder.init();
}
// JSON format log output example
fn setup_json_logger() {
use serde_json::json;
let mut builder = Builder::new();
builder.format(|buf, record| {
let log_entry = json!({
"timestamp": chrono::Utc::now().to_rfc3339(),
"level": record.level().to_string(),
"target": record.target(),
"module": record.module_path(),
"file": record.file(),
"line": record.line(),
"message": record.args().to_string()
});
writeln!(buf, "{}", log_entry)
});
if let Ok(rust_log) = std::env::var("RUST_LOG") {
builder.parse_filters(&rust_log);
}
builder.init();
}
// Usage example
fn custom_logger_example() {
setup_detailed_logger();
log::info!("Info log with custom logger");
log::error!("Error log with custom logger");
log::debug!("Debug log with custom logger");
}
Practical Application Example
// Cargo.toml
/*
[dependencies]
log = "0.4"
env_logger = "0.10"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
*/
use log::{info, warn, error, debug};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Debug, Clone)]
struct User {
id: u32,
name: String,
email: String,
}
struct UserService {
users: Arc<Mutex<HashMap<u32, User>>>,
}
impl UserService {
fn new() -> Self {
info!("UserService initialization");
Self {
users: Arc::new(Mutex::new(HashMap::new())),
}
}
async fn create_user(&self, name: String, email: String) -> Result<User, String> {
info!("User creation started: name={}, email={}", name, email);
// Input validation
if name.is_empty() {
warn!("User creation failed: name is empty");
return Err("Name cannot be empty".to_string());
}
if !email.contains('@') {
warn!("User creation failed: invalid email address - {}", email);
return Err("Invalid email address".to_string());
}
let mut users = self.users.lock().await;
// Duplicate check
for (_, existing_user) in users.iter() {
if existing_user.email == email {
warn!("User creation failed: email already exists - {}", email);
return Err("Email already exists".to_string());
}
}
// Generate new user ID
let new_id = users.len() as u32 + 1;
let user = User {
id: new_id,
name: name.clone(),
email: email.clone(),
};
users.insert(new_id, user.clone());
info!("User creation successful: ID={}, name={}", new_id, name);
debug!("Current user count: {}", users.len());
Ok(user)
}
async fn get_user(&self, id: u32) -> Option<User> {
debug!("User retrieval: ID={}", id);
let users = self.users.lock().await;
match users.get(&id) {
Some(user) => {
debug!("User found: {}", user.name);
Some(user.clone())
}
None => {
warn!("User not found: ID={}", id);
None
}
}
}
async fn list_users(&self) -> Vec<User> {
debug!("Retrieving all users");
let users = self.users.lock().await;
let user_list: Vec<User> = users.values().cloned().collect();
info!("User list retrieval completed: {} users", user_list.len());
user_list
}
async fn delete_user(&self, id: u32) -> Result<(), String> {
info!("User deletion started: ID={}", id);
let mut users = self.users.lock().await;
match users.remove(&id) {
Some(user) => {
info!("User deletion successful: ID={}, name={}", id, user.name);
debug!("Remaining user count: {}", users.len());
Ok(())
}
None => {
warn!("User to delete not found: ID={}", id);
Err("User not found".to_string())
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Log initialization
env_logger::init();
info!("Application started");
let user_service = UserService::new();
// User creation test
let users_to_create = vec![
("John Doe", "[email protected]"),
("Jane Smith", "[email protected]"),
("Bob Wilson", "[email protected]"),
];
for (name, email) in users_to_create {
match user_service.create_user(name.to_string(), email.to_string()).await {
Ok(user) => {
info!("User creation successful: {:?}", user);
}
Err(e) => {
error!("User creation error: {}", e);
}
}
}
// Duplicate creation test
match user_service.create_user("Duplicate User".to_string(), "[email protected]".to_string()).await {
Ok(_) => {
error!("Duplicate check failed");
}
Err(e) => {
info!("Duplicate check working correctly: {}", e);
}
}
// Retrieve user list
let all_users = user_service.list_users().await;
for user in all_users {
debug!("Registered user: {:?}", user);
}
// Individual user retrieval
if let Some(user) = user_service.get_user(1).await {
info!("User retrieval successful: {:?}", user);
}
// Retrieve non-existent user
if user_service.get_user(999).await.is_none() {
debug!("Non-existent user retrieval test successful");
}
// User deletion
match user_service.delete_user(2).await {
Ok(_) => {
info!("User deletion successful");
}
Err(e) => {
error!("User deletion error: {}", e);
}
}
// Final state check
let final_users = user_service.list_users().await;
info!("Final user count: {}", final_users.len());
info!("Application terminated");
Ok(())
}
// Execution examples and log control
// RUST_LOG=info cargo run
// RUST_LOG=debug cargo run
// RUST_LOG=myapp=trace cargo run
// RUST_LOG=info,tokio=warn cargo run
Production Environment Configuration
use env_logger::{Builder, Env};
use log::LevelFilter;
use std::env;
use std::fs::OpenOptions;
use std::io::Write;
fn setup_production_logger() -> Result<(), Box<dyn std::error::Error>> {
let mut builder = Builder::new();
// Environment-based default level setting
let default_level = match env::var("ENVIRONMENT").as_deref() {
Ok("production") => "warn",
Ok("staging") => "info",
Ok("development") => "debug",
_ => "info",
};
// Configuration from environment variables (with default values)
let env = Env::default()
.filter_or("RUST_LOG", default_level)
.write_style_or("RUST_LOG_STYLE", "auto");
builder.from_env(env);
// Format settings for production environment
if env::var("ENVIRONMENT").as_deref() == Ok("production") {
// JSON format log output (for log aggregation systems)
builder.format(|buf, record| {
let log_entry = serde_json::json!({
"timestamp": chrono::Utc::now().to_rfc3339(),
"level": record.level().to_string(),
"target": record.target(),
"message": record.args().to_string(),
"module": record.module_path(),
"file": record.file(),
"line": record.line()
});
writeln!(buf, "{}", log_entry)
});
} else {
// Readable format for development environment
builder.format(|buf, record| {
writeln!(
buf,
"{} [{}] {} - {}",
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.target(),
record.args()
)
});
}
builder.init();
Ok(())
}
// Error handling and recovery
fn safe_logger_init() {
match setup_production_logger() {
Ok(_) => {
log::info!("Logger initialization successful");
}
Err(e) => {
eprintln!("Logger initialization failed: {} - fallback initialization", e);
// Fallback: basic initialization
env_logger::init();
log::warn!("Initialized with fallback logger");
}
}
}
// Configuration validation and health check
fn validate_logging_config() -> Result<(), String> {
// Check required environment variables
let required_vars = ["RUST_LOG"];
for var in required_vars.iter() {
if env::var(var).is_err() {
return Err(format!("Required environment variable {} is not set", var));
}
}
// Validate log level
if let Ok(log_level) = env::var("RUST_LOG") {
let valid_levels = ["error", "warn", "info", "debug", "trace"];
if !valid_levels.iter().any(|&level| log_level.contains(level)) {
return Err(format!("Invalid log level in RUST_LOG: {}", log_level));
}
}
Ok(())
}
fn main() {
// Configuration validation
if let Err(e) = validate_logging_config() {
eprintln!("Log configuration error: {}", e);
std::process::exit(1);
}
// Safe initialization
safe_logger_init();
log::info!("Application started in production environment");
// Application logic
log::debug!("Debug information (not displayed in production)");
log::info!("Important business log");
log::warn!("Situation requiring attention");
log::error!("An error occurred");
log::info!("Application terminated normally");
}
// Execution examples
// ENVIRONMENT=production RUST_LOG=warn cargo run
// ENVIRONMENT=development RUST_LOG=debug cargo run
// RUST_LOG=myapp=info,tokio=error ENVIRONMENT=staging cargo run