log

Lightweight logging facade maintained by Rust core team. Provides single API abstracting actual logging implementation. Manages log levels with five macros: error!, warn!, info!, debug!, trace!. Standard choice for library development.

Logging LibraryRustFacadeLightweightStandardMacros

Library

log

Overview

log is a lightweight logging facade library maintained by the Rust core team. It provides a single API that abstracts actual logging implementations and manages log levels with five macros: error!, warn!, info!, debug!, and trace!. It functions as the de facto standard logging interface in the Rust ecosystem and has established a firm position as the recommended choice for library development.

Details

The log crate continues to maintain its unshakeable position as the foundation of the Rust ecosystem in 2025. As the first crate introduced in the Rust Cookbook, it serves as a learning starting point for new Rust developers, and linking only to the log crate in library development has become a standard practice. Through the facade pattern, actual log output is handled by implementation crates like env_logger, log4rs, and tracing, providing flexibility for application developers to choose specific loggers.

Key Features

  • Lightweight Facade: Implementation-independent abstract logging interface
  • 5-Level Logging: Standard levels of error, warn, info, debug, trace
  • Macro-based API: Compile-time optimization and zero-cost abstractions
  • Implementation Neutral: Combinable with any logger implementation
  • Structured Logging Support: Structured data support via kv features
  • no_std Support: Usable in embedded environments
  • Rust Core Team Maintenance: Long-term stability and quality assurance

Pros & Cons

Pros

  • Universality as the standard logging interface in the Rust ecosystem
  • Implementation choice flexibility and ecosystem compatibility during library development
  • High performance through lightweight and minimal dependencies
  • Compile-time optimization and zero-cost abstraction realization
  • Long-term support and stability guarantee by the Rust core team
  • Abundant documentation and comprehensive learning resources

Cons

  • Cannot output actual logs alone due to being a facade
  • Requires separate selection and configuration of implementation crates like env_logger
  • Limited structured logging, not as feature-rich as tracing
  • Limited asynchronous logging and context management capabilities
  • Additional implementation required for complex log formatting or filtering
  • Advanced feature limitations due to simplicity

Reference Pages

Code Examples

Basic Setup and Log Macros

// Cargo.toml
[dependencies]
log = "0.4"
env_logger = "0.10"  // Example implementation crate

// main.rs
use log::{debug, error, info, trace, warn};

fn main() {
    // Initialize logger (implementation crate dependent)
    env_logger::init();
    
    // Basic usage of five log level macros
    error!("Critical error occurred");
    warn!("Warning: Configuration file not found");
    info!("Application started");
    debug!("Debug info: variable x = {}", 42);
    trace!("Detailed trace: function call");
    
    // Conditional logging (compile-time optimization)
    if log::log_enabled!(log::Level::Debug) {
        let expensive_computation = calculate_heavy_task();
        debug!("Calculation result: {}", expensive_computation);
    }
}

fn calculate_heavy_task() -> u32 {
    // Heavy computation simulation
    (1..=1000).sum()
}

Library Usage Pattern with log

// Recommended pattern for library crates
use log::{debug, error, info, trace, warn};

pub struct YakShaver {
    pub name: String,
}

impl YakShaver {
    pub fn new(name: String) -> Self {
        info!("Created YakShaver '{}'", name);
        Self { name }
    }
    
    pub fn shave_yak(&mut self, yak: &mut Yak) -> Result<(), ShavingError> {
        trace!("Yak shaving started: {}", yak.name);
        
        loop {
            match self.find_razor() {
                Ok(razor) => {
                    info!("Razor found: {:?}", razor);
                    yak.shave(razor)?;
                    info!("Yak shaving completed: {}", yak.name);
                    break;
                }
                Err(err) => {
                    warn!("Unable to find razor: {}, retrying...", err);
                    std::thread::sleep(std::time::Duration::from_millis(100));
                }
            }
        }
        
        debug!("Shaving statistics: {} attempts", self.attempt_count);
        Ok(())
    }
    
    fn find_razor(&self) -> Result<Razor, String> {
        // Razor search logic
        if rand::random::<f32>() > 0.7 {
            Ok(Razor::Sharp)
        } else {
            Err("Razor not found".to_string())
        }
    }
}

#[derive(Debug)]
pub enum Razor {
    Sharp,
    Dull,
}

pub struct Yak {
    pub name: String,
    shaved: bool,
}

impl Yak {
    pub fn new(name: String) -> Self {
        Self { name, shaved: false }
    }
    
    pub fn shave(&mut self, razor: Razor) -> Result<(), ShavingError> {
        match razor {
            Razor::Sharp => {
                self.shaved = true;
                debug!("Successful shaving of yak '{}'", self.name);
                Ok(())
            }
            Razor::Dull => {
                error!("Failed to shave with dull razor: {}", self.name);
                Err(ShavingError::DullRazor)
            }
        }
    }
}

#[derive(Debug)]
pub enum ShavingError {
    DullRazor,
    YakEscaped,
}

impl std::fmt::Display for ShavingError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ShavingError::DullRazor => write!(f, "Razor is too dull"),
            ShavingError::YakEscaped => write!(f, "Yak escaped"),
        }
    }
}

impl std::error::Error for ShavingError {}

Target and Log Filtering

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

// Module-specific log targets usage
pub mod database {
    use log::{debug, error, info};
    
    pub fn connect() -> Result<Connection, DbError> {
        info!(target: "database", "Database connection started");
        
        match establish_connection() {
            Ok(conn) => {
                info!(target: "database", "Database connection successful");
                debug!(target: "database", "Connection details: {:?}", conn);
                Ok(conn)
            }
            Err(e) => {
                error!(target: "database", "Database connection failed: {}", e);
                Err(e)
            }
        }
    }
    
    pub fn query(sql: &str) -> Result<Vec<Row>, DbError> {
        debug!(target: "database::query", "Executing SQL query: {}", sql);
        
        let start = std::time::Instant::now();
        let result = execute_query(sql);
        let duration = start.elapsed();
        
        match result {
            Ok(rows) => {
                info!(target: "database::query", 
                     "Query successful: {} rows retrieved in {}ms", rows.len(), duration.as_millis());
                debug!(target: "database::query", "Results: {:?}", rows);
                Ok(rows)
            }
            Err(e) => {
                error!(target: "database::query", 
                      "Query error: {} (execution time: {}ms)", e, duration.as_millis());
                Err(e)
            }
        }
    }
    
    // Dummy implementations
    struct Connection;
    struct Row;
    #[derive(Debug)]
    struct DbError(String);
    
    impl std::fmt::Display for DbError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "{}", self.0)
        }
    }
    
    impl std::error::Error for DbError {}
    
    fn establish_connection() -> Result<Connection, DbError> {
        if rand::random::<f32>() > 0.2 {
            Ok(Connection)
        } else {
            Err(DbError("Connection error".to_string()))
        }
    }
    
    fn execute_query(_sql: &str) -> Result<Vec<Row>, DbError> {
        if rand::random::<f32>() > 0.1 {
            Ok(vec![Row; rand::random::<usize>() % 100])
        } else {
            Err(DbError("SQL error".to_string()))
        }
    }
}

pub mod network {
    use log::{debug, error, info, warn};
    
    pub async fn fetch_data(url: &str) -> Result<String, NetworkError> {
        info!(target: "network", "HTTP request started: {}", url);
        
        if !is_valid_url(url) {
            warn!(target: "network", "Invalid URL: {}", url);
            return Err(NetworkError::InvalidUrl);
        }
        
        let start = std::time::Instant::now();
        
        // HTTP request simulation
        let result = perform_request(url).await;
        let duration = start.elapsed();
        
        match result {
            Ok(data) => {
                info!(target: "network", 
                     "HTTP request successful: {} bytes retrieved in {}ms", 
                     data.len(), duration.as_millis());
                debug!(target: "network", "Response data: {}", 
                      if data.len() > 100 { 
                          format!("{}...", &data[..100]) 
                      } else { 
                          data.clone() 
                      });
                Ok(data)
            }
            Err(e) => {
                error!(target: "network", 
                      "HTTP request failed: {} (execution time: {}ms)", 
                      e, duration.as_millis());
                Err(e)
            }
        }
    }
    
    #[derive(Debug)]
    pub enum NetworkError {
        InvalidUrl,
        Timeout,
        ServerError,
    }
    
    impl std::fmt::Display for NetworkError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                NetworkError::InvalidUrl => write!(f, "Invalid URL"),
                NetworkError::Timeout => write!(f, "Timeout"),
                NetworkError::ServerError => write!(f, "Server error"),
            }
        }
    }
    
    impl std::error::Error for NetworkError {}
    
    fn is_valid_url(url: &str) -> bool {
        url.starts_with("http")
    }
    
    async fn perform_request(url: &str) -> Result<String, NetworkError> {
        // Request simulation
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
        
        if url.contains("timeout") {
            Err(NetworkError::Timeout)
        } else if url.contains("error") {
            Err(NetworkError::ServerError)
        } else {
            Ok(format!("Response data from {}", url))
        }
    }
}

// Main function with integrated usage example
#[tokio::main]
async fn main() {
    env_logger::init();
    
    info!("Application started");
    
    // Database operations
    if let Err(e) = database::connect() {
        error!("Database connection error: {}", e);
    } else if let Err(e) = database::query("SELECT * FROM users") {
        error!("Query error: {}", e);
    }
    
    // Network operations
    let urls = vec![
        "https://api.example.com/users",
        "https://api.example.com/timeout",
        "invalid-url",
        "https://api.example.com/error",
    ];
    
    for url in urls {
        if let Err(e) = network::fetch_data(url).await {
            warn!("Network error for {}: {}", url, e);
        }
    }
    
    info!("Application finished");
}

Structured Logging and Custom Data (kv feature)

// Cargo.toml
[dependencies]
log = { version = "0.4", features = ["kv"] }
env_logger = "0.10"
serde = { version = "1.0", features = ["derive"] }

use log::{as_debug, as_error, as_serde, debug, error, info, warn};
use serde::Serialize;

#[derive(Debug, Serialize)]
struct User {
    id: u64,
    name: String,
    email: String,
}

#[derive(Debug, Serialize)]
struct RequestMetrics {
    endpoint: String,
    method: String,
    status_code: u16,
    duration_ms: u64,
    user_id: Option<u64>,
}

impl User {
    fn new(id: u64, name: String, email: String) -> Self {
        let user = Self { id, name, email };
        
        // Log user creation as structured log
        info!(
            user_id = user.id,
            user_name = user.name.as_str(),
            action = "user_created";
            "New user created"
        );
        
        user
    }
    
    fn update_email(&mut self, new_email: String) -> Result<(), &'static str> {
        let old_email = self.email.clone();
        
        if !is_valid_email(&new_email) {
            warn!(
                user_id = self.id,
                old_email = old_email.as_str(),
                new_email = new_email.as_str(),
                action = "email_update_failed";
                "Email update failed due to invalid email address"
            );
            return Err("Invalid email address");
        }
        
        self.email = new_email.clone();
        
        info!(
            user_id = self.id,
            old_email = old_email.as_str(),
            new_email = new_email.as_str(),
            action = "email_updated";
            "User email address updated"
        );
        
        Ok(())
    }
}

// HTTP request processing example
async fn handle_request(
    method: &str,
    endpoint: &str,
    user_id: Option<u64>,
) -> Result<String, Box<dyn std::error::Error>> {
    let start_time = std::time::Instant::now();
    
    // Request start log
    info!(
        endpoint = endpoint,
        method = method,
        user_id = user_id,
        action = "request_started";
        "HTTP request processing started"
    );
    
    // Actual processing (simulation)
    let result = process_request(method, endpoint, user_id).await;
    
    let duration = start_time.elapsed();
    let status_code = match &result {
        Ok(_) => 200,
        Err(_) => 500,
    };
    
    // Create metrics struct
    let metrics = RequestMetrics {
        endpoint: endpoint.to_string(),
        method: method.to_string(),
        status_code,
        duration_ms: duration.as_millis() as u64,
        user_id,
    };
    
    match &result {
        Ok(response) => {
            info!(
                metrics:serde = metrics,
                response_size = response.len(),
                action = "request_completed";
                "HTTP request processing completed"
            );
        }
        Err(error) => {
            error!(
                metrics:serde = metrics,
                error:err = error.as_ref(),
                action = "request_failed";
                "HTTP request processing failed"
            );
        }
    }
    
    result
}

async fn process_request(
    method: &str,
    endpoint: &str,
    user_id: Option<u64>,
) -> Result<String, Box<dyn std::error::Error>> {
    // Request processing simulation
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
    
    match endpoint {
        "/api/users" if method == "GET" => {
            debug!(
                endpoint = endpoint,
                method = method,
                action = "database_query";
                "Database query for user list retrieval"
            );
            Ok("User list data".to_string())
        }
        "/api/profile" if method == "GET" => {
            if let Some(uid) = user_id {
                debug!(
                    endpoint = endpoint,
                    method = method,
                    user_id = uid,
                    action = "profile_fetch";
                    "User profile retrieval"
                );
                Ok(format!("Profile for user {}", uid))
            } else {
                Err("Authentication required".into())
            }
        }
        "/api/error" => {
            Err("Simulation error".into())
        }
        _ => {
            warn!(
                endpoint = endpoint,
                method = method,
                action = "unknown_endpoint";
                "Unknown endpoint"
            );
            Err("Unknown endpoint".into())
        }
    }
}

fn is_valid_email(email: &str) -> bool {
    email.contains('@') && email.contains('.')
}

// Application state monitoring and logging
struct AppState {
    active_connections: u32,
    memory_usage_mb: u64,
    cpu_usage_percent: f32,
}

impl AppState {
    fn new() -> Self {
        Self {
            active_connections: 0,
            memory_usage_mb: 0,
            cpu_usage_percent: 0.0,
        }
    }
    
    fn update_metrics(&mut self) {
        // Metrics update simulation
        self.active_connections = rand::random::<u32>() % 1000;
        self.memory_usage_mb = 100 + (rand::random::<u64>() % 400);
        self.cpu_usage_percent = rand::random::<f32>() * 100.0;
        
        debug!(
            active_connections = self.active_connections,
            memory_usage_mb = self.memory_usage_mb,
            cpu_usage_percent = self.cpu_usage_percent,
            action = "metrics_updated";
            "Application metrics updated"
        );
        
        // Threshold checks
        if self.memory_usage_mb > 400 {
            warn!(
                memory_usage_mb = self.memory_usage_mb,
                threshold = 400,
                action = "high_memory_usage";
                "Memory usage exceeded threshold"
            );
        }
        
        if self.cpu_usage_percent > 80.0 {
            warn!(
                cpu_usage_percent = self.cpu_usage_percent,
                threshold = 80.0,
                action = "high_cpu_usage";
                "High CPU usage detected"
            );
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    env_logger::init();
    
    info!(action = "app_started"; "Application started");
    
    // User management test
    let mut user = User::new(1, "John Doe".to_string(), "[email protected]".to_string());
    user.update_email("[email protected]".to_string())?;
    let _ = user.update_email("invalid-email"); // Error case
    
    // HTTP request processing test
    let requests = vec![
        ("GET", "/api/users", None),
        ("GET", "/api/profile", Some(1)),
        ("GET", "/api/profile", None), // Authentication error
        ("GET", "/api/error", Some(1)), // Server error
        ("POST", "/api/unknown", Some(1)), // Unknown endpoint
    ];
    
    for (method, endpoint, user_id) in requests {
        let _ = handle_request(method, endpoint, user_id).await;
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    }
    
    // Application state monitoring test
    let mut app_state = AppState::new();
    for _ in 0..5 {
        app_state.update_metrics();
        tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
    }
    
    info!(action = "app_finished"; "Application finished");
    Ok(())
}

Log Level Control and Performance Optimization

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

// Performance-focused log control
struct PerformanceCriticalService {
    operation_count: u64,
    last_log_time: std::time::Instant,
}

impl PerformanceCriticalService {
    fn new() -> Self {
        Self {
            operation_count: 0,
            last_log_time: std::time::Instant::now(),
        }
    }
    
    fn process_item(&mut self, item: &str) -> Result<String, ProcessError> {
        self.operation_count += 1;
        
        // Compile-time optimization: expensive_debug_info() is not called
        // when debug logging is disabled
        debug!("Processing started: item={}, debug_info={}", 
               item, 
               if log::log_enabled!(Level::Debug) { 
                   expensive_debug_info() 
               } else { 
                   String::new() 
               });
        
        // Batch logging: statistics every second
        if self.last_log_time.elapsed() >= std::time::Duration::from_secs(1) {
            info!("Processing statistics: {} operations/sec", self.operation_count);
            self.operation_count = 0;
            self.last_log_time = std::time::Instant::now();
        }
        
        // Actual processing
        if item.is_empty() {
            warn!("Empty item was provided");
            return Err(ProcessError::EmptyItem);
        }
        
        if item.len() > 1000 {
            error!("Item size exceeds limit: {}", item.len());
            return Err(ProcessError::OversizedItem);
        }
        
        // Heavy processing simulation
        let result = format!("Processed: {}", item);
        
        // Trace level: most detailed logging
        trace!("Processing details: input_len={}, output_len={}, operation_id={}", 
               item.len(), result.len(), self.operation_count);
        
        Ok(result)
    }
    
    fn bulk_process(&mut self, items: Vec<&str>) -> Vec<Result<String, ProcessError>> {
        info!("Bulk processing started: {} items", items.len());
        
        let start = std::time::Instant::now();
        let results: Vec<_> = items.into_iter()
            .map(|item| self.process_item(item))
            .collect();
        
        let duration = start.elapsed();
        let success_count = results.iter().filter(|r| r.is_ok()).count();
        let error_count = results.len() - success_count;
        
        info!("Bulk processing completed: success={}, errors={}, duration={}ms", 
              success_count, error_count, duration.as_millis());
        
        if error_count > 0 {
            warn!("Errors occurred: {}/{} items", error_count, results.len());
        }
        
        results
    }
}

#[derive(Debug)]
enum ProcessError {
    EmptyItem,
    OversizedItem,
}

impl std::fmt::Display for ProcessError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ProcessError::EmptyItem => write!(f, "Empty item"),
            ProcessError::OversizedItem => write!(f, "Oversized item"),
        }
    }
}

impl std::error::Error for ProcessError {}

fn expensive_debug_info() -> String {
    // Heavy computation simulation (executed only during debugging)
    let start = std::time::Instant::now();
    let sum: u64 = (1..=1000).map(|x| x * x).sum();
    let duration = start.elapsed();
    format!("debug_computation: sum={}, time={}μs", sum, duration.as_micros())
}

// Custom logger implementation example
struct CustomLogger {
    level: Level,
    module_filters: std::collections::HashMap<String, Level>,
}

impl CustomLogger {
    fn new(default_level: Level) -> Self {
        Self {
            level: default_level,
            module_filters: std::collections::HashMap::new(),
        }
    }
    
    fn set_module_level(&mut self, module: String, level: Level) {
        self.module_filters.insert(module, level);
    }
    
    fn should_log(&self, metadata: &log::Metadata) -> bool {
        let target_level = self.module_filters
            .get(metadata.target())
            .copied()
            .unwrap_or(self.level);
        
        metadata.level() <= target_level
    }
}

impl log::Log for CustomLogger {
    fn enabled(&self, metadata: &log::Metadata) -> bool {
        self.should_log(metadata)
    }
    
    fn log(&self, record: &log::Record) {
        if !self.should_log(record.metadata()) {
            return;
        }
        
        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs();
        
        println!("[{}] {} [{}:{}] {}", 
                timestamp,
                record.level(),
                record.module_path().unwrap_or("unknown"),
                record.line().unwrap_or(0),
                record.args());
    }
    
    fn flush(&self) {}
}

fn main() {
    // Custom logger configuration
    let mut logger = CustomLogger::new(Level::Info);
    logger.set_module_level("performance_test".to_string(), Level::Debug);
    logger.set_module_level("verbose_module".to_string(), Level::Trace);
    
    log::set_boxed_logger(Box::new(logger))
        .map(|()| log::set_max_level(log::LevelFilter::Trace))
        .expect("Logger initialization failed");
    
    info!("Application started with custom logger");
    
    // Performance test
    let mut service = PerformanceCriticalService::new();
    
    // Individual processing test
    let test_items = vec!["item1", "", "item3", &"x".repeat(2000)];
    for item in test_items {
        match service.process_item(item) {
            Ok(result) => debug!("Processing successful: {}", result),
            Err(e) => warn!("Processing error: {}", e),
        }
    }
    
    // Bulk processing test
    let bulk_items = (1..=100)
        .map(|i| format!("bulk_item_{}", i))
        .collect::<Vec<_>>();
    let bulk_refs: Vec<&str> = bulk_items.iter().map(|s| s.as_str()).collect();
    
    let results = service.bulk_process(bulk_refs);
    info!("Bulk processing results: {}/{} successful", 
          results.iter().filter(|r| r.is_ok()).count(),
          results.len());
    
    // Load test (heavy logging)
    info!("Load test started");
    let start = std::time::Instant::now();
    
    for i in 0..10000 {
        if i % 1000 == 0 {
            info!("Load test progress: {}/10000", i);
        }
        
        // Debug logs are usually disabled so no performance impact
        debug!("Load test iteration: {}", i);
        
        // Trace logs similarly
        trace!("Detailed trace: iteration={}, timestamp={}", i, start.elapsed().as_millis());
    }
    
    let duration = start.elapsed();
    info!("Load test completed: processed 10000 logs in {}ms", duration.as_millis());
    
    info!("Application finished");
}