Loading page…
Rust walkthroughs
Loading page…
Log is a lightweight logging facade for Rust. It provides a single logging API that abstracts over the actual logging implementation. Applications can choose any logging backend (like env_logger, simple_logger, or tracing) while library authors can use log without forcing a specific implementation on users.
Key concepts:
error!, warn!, info!, debug!, trace!When to use Log:
When NOT to use Log:
tracing instead)use log::{error, warn, info, debug, trace};
fn main() {
// Initialize a logger (required!)
// Without this, logs are silently discarded
env_logger::init();
// Log at different levels
error!("This is an error");
warn!("This is a warning");
info!("This is info");
debug!("This is debug");
trace!("This is trace");
}use log::{info, debug};
fn main() {
env_logger::init();
let user = "Alice";
let count = 42;
// Format string (like println!)
info!("User {} has {} items", user, count);
// Named arguments
debug!(user = %user, count = count; "Processing user");
}use log::{Level, LevelFilter};
fn main() {
// Available levels (in order of severity)
println!("Log levels:");
println!(" Error: {}", Level::Error as i32); // 1
println!(" Warn: {}", Level::Warn as i32); // 2
println!(" Info: {}", Level::Info as i32); // 3
println!(" Debug: {}", Level::Debug as i32); // 4
println!(" Trace: {}", Level::Trace as i32); // 5
// LevelFilter for configuring logger
println!("\nLevelFilter values:");
println!(" Off: {}", LevelFilter::Off as i32);
println!(" Error: {}", LevelFilter::Error as i32);
println!(" Warn: {}", LevelFilter::Warn as i32);
println!(" Info: {}", LevelFilter::Info as i32);
println!(" Debug: {}", LevelFilter::Debug as i32);
println!(" Trace: {}", LevelFilter::Trace as i32);
}use log::{info, debug, error};
fn process_data(data: &[u8]) -> Result<(), &'static str> {
debug!("Processing {} bytes", data.len());
if data.is_empty() {
error!("Empty data provided");
return Err("empty data");
}
info!("Data processed successfully");
Ok(())
}
fn main() {
env_logger::init();
process_data(&[1, 2, 3]).unwrap();
process_data(&[]).unwrap_err();
}use log::{info, debug, error};
fn main() {
// Initialize with custom builder
env_logger::Builder::new()
.filter_level(log::LevelFilter::Debug) // Default level
.init();
info!("Application started");
debug!("Debug information");
// Run with: RUST_LOG=debug cargo run
// Or: RUST_LOG=my_app=debug cargo run
}use log::{info, debug, trace};
mod database {
pub fn connect() {
super::debug!("Connecting to database");
}
}
mod cache {
pub fn get(key: &str) {
super::trace!("Cache get: {}", key);
}
}
fn main() {
// Filter by module path
env_logger::Builder::new()
.filter_module("my_app::database", log::LevelFilter::Debug)
.filter_module("my_app::cache", log::LevelFilter::Trace)
.init();
database::connect();
cache::get("user:1");
}use log::{info, error};
fn main() {
env_logger::init();
// Default target is module path
info!("Default target");
// Custom target
info!(target: "custom_target", "Custom target message");
// Useful for routing logs
error!(target: "security", "Security alert!");
}use log::{info, warn, error};
fn main() {
env_logger::init();
let user_id = 42;
let action = "login";
let ip = "192.168.1.1";
// Key-value pairs (requires kv feature)
info!(
user_id = user_id,
action = action,
ip = ip;
"User action performed"
);
// Error with context
error!(
error_code = 500,
endpoint = "/api/users";
"Request failed"
);
}use log::{error, warn, info};
use std::io;
fn read_config(path: &str) -> Result<String, io::Error> {
std::fs::read_to_string(path)
}
fn main() {
env_logger::init();
match read_config("config.txt") {
Ok(config) => info!("Config loaded: {} bytes", config.len()),
Err(e) => error!("Failed to read config: {}", e),
}
}use log::info;
fn main() {
// Alternative to env_logger - simpler setup
simple_logger::SimpleLogger::new()
.env()
.init()
.unwrap();
info!("Using simple_logger");
}use log::{info, LevelFilter};
fn main() {
// Fern - more configuration options
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{} {} {}] {}",
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.target(),
message
))
})
.level(LevelFilter::Debug)
.chain(std::io::stdout())
.apply()
.unwrap();
info!("Using fern logger");
}use log::{info, LevelFilter};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let log_file = std::fs::File::create("app.log")?;
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{} {}] {}",
record.level(),
record.target(),
message
))
})
.level(LevelFilter::Debug)
.chain(log_file)
.apply()?;
info!("This goes to file");
Ok(())
}use log::{info, LevelFilter, Level};
fn main() {
fern::Dispatch::new()
// Errors go to stderr
.chain(
fern::Dispatch::new()
.level(LevelFilter::Error)
.chain(std::io::stderr())
)
// Info and below go to stdout
.chain(
fern::Dispatch::new()
.level(LevelFilter::Info)
.chain(std::io::stdout())
)
.apply()
.unwrap();
info!("Info to stdout");
log::error!("Error to stderr");
}use log::{info, debug, trace};
fn expensive_computation() -> String {
// Simulate expensive work
std::thread::sleep(std::time::Duration::from_millis(100));
"result".to_string()
}
fn main() {
env_logger::init();
// BAD: expensive_computation() always runs
debug!("Result: {}", expensive_computation());
// GOOD: Check level first
if log::log_enabled!(log::Level::Debug) {
debug!("Result: {}", expensive_computation());
}
}use log::{Log, Metadata, Record, LevelFilter};
struct SimpleLogger;
impl Log for SimpleLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= LevelFilter::Debug
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
println!("[{}] {}", record.level(), record.args());
}
}
fn flush(&self) {
// Flush any buffers
}
}
fn main() {
let logger = SimpleLogger;
log::set_boxed_logger(Box::new(logger))
.map(|()| log::set_max_level(LevelFilter::Debug))
.unwrap();
log::info!("Custom logger works!");
log::debug!("Debug message");
}// In your library crate:
use log::{debug, info, warn};
pub struct Config {
pub debug_mode: bool,
}
impl Config {
pub fn new() -> Self {
debug!("Creating new config");
Self { debug_mode: false }
}
pub fn validate(&self) -> Result<(), String> {
info!("Validating config");
if self.debug_mode {
warn!("Debug mode enabled");
}
Ok(())
}
}
// Note: Libraries should NOT initialize the logger!
// Let the application choose the backend.
fn main() {
env_logger::init();
// Access record metadata in custom logger
log::logger().log(
&Record::builder()
.args(format_args!("Custom log message"))
.level(Level::Info)
.target("my_target")
.module_path_static(Some("my_app::main"))
.file_static(Some(file!()))
.line(Some(line!()))
.build()
);
}use log::{info, debug, error};
fn process_request(user_id: u64, action: &str) {
debug!(user_id = user_id, action = action; "Processing request");
// Do work...
info!(user_id = user_id, action = action; "Request completed");
}
fn main() {
env_logger::init();
process_request(42, "update_profile");
}use log::{info, error};
use std::sync::Arc;
use std::thread;
fn main() {
env_logger::init();
let data = Arc::new(vec![1, 2, 3]);
let handles: Vec<_> = (0..3)
.map(|i| {
let data = Arc::clone(&data);
thread::spawn(move || {
info!("Thread {} processing", i);
// Work...
info!("Thread {} done", i);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}// In your Cargo.toml or shell:
// RUST_LOG=debug - Show debug and above
// RUST_LOG=trace - Show all logs
// RUST_LOG=my_app=debug - Debug for specific module
// RUST_LOG=my_app::db=trace - Trace for db module
// RUST_LOG=warn,my_app=debug - Warn globally, debug for my_app
use log::{info, debug, trace};
fn main() {
// Parse RUST_LOG environment variable
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
.init();
info!("Application started");
debug!("Debug info (set RUST_LOG=debug to see)");
trace!("Trace info (set RUST_LOG=trace to see)");
}use log::{info, debug};
fn calculate(x: i32, y: i32) -> i32 {
debug!("Calculating {} + {}", x, y);
x + y
}
#[cfg(test)]
mod tests {
use super::*;
fn init_test_logger() {
let _ = env_logger::builder()
.is_test(true)
.filter_level(log::LevelFilter::Debug)
.try_init();
}
#[test]
fn test_calculate() {
init_test_logger();
assert_eq!(calculate(2, 3), 5);
}
}
fn main() {
env_logger::init();
println!("Result: {}", calculate(5, 7));
}use log::{info, LevelFilter};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let today = chrono::Local::now().format("%Y-%m-%d");
let log_path = format!("logs/app-{}.log", today);
std::fs::create_dir_all("logs")?;
let log_file = std::fs::File::create(&log_path)?;
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{} {}] {}",
chrono::Local::now().format("%H:%M:%S"),
record.level(),
message
))
})
.level(LevelFilter::Info)
.chain(log_file)
.apply()?;
info!("Logging to {}", log_path);
Ok(())
}Log Key Imports:
use log::{error, warn, info, debug, trace, Level, LevelFilter};Log Levels (in order):
| Level | Macro | Description |
|-------|-------|-------------|
| Error | error! | Errors, critical issues |
| Warn | warn! | Warnings, potential issues |
| Info | info! | General information |
| Debug | debug! | Debug information |
| Trace | trace! | Very detailed tracing |
Common Backends:
| Crate | Description |
|-------|-------------|
| env_logger | Standard, uses RUST_LOG env var |
| simple_logger | Simple setup |
| fern | Highly configurable |
| flexi_logger | Flexible, supports rotation |
Environment Variables:
# Set log level
RUST_LOG=debug cargo run
# Per-module
RUST_LOG=my_app=debug,my_app::db=trace cargo run
# Multiple modules
RUST_LOG=warn,my_app=debug cargo runKey Points:
log, not initialize loggerlog_enabled! for expensive computationstarget: "name" for routing to specific outputskey = value; syntaxenv_logger::init() required to see logsenv_logger::builder().is_test(true)Best Practices:
log_enabled! for expensive formatting