Loading page…
Rust walkthroughs
Loading page…
The log crate provides a lightweight logging facade for Rust applications. It defines logging macros (error!, warn!, info!, debug!, trace!) but doesn't implement actual output—you choose a logger implementation like env_logger, simple_logger, or fern. This separation lets libraries log without forcing a specific logging backend on applications.
Key concepts:
error, warn, info, debug, trace (in descending priority)error!, warn!, info!, debug!, trace! for each levellog crate provides the API, implementations provide the outputRUST_LOG environment variableThe log crate is the standard logging interface used by most Rust libraries.
# Cargo.toml
[dependencies]
log = "0.4"
env_logger = "0.10"use log::{error, warn, info, debug, trace};
fn main() {
// Initialize the logger
env_logger::init();
// Log at different levels
error!("This is an error message");
warn!("This is a warning message");
info!("This is an info message");
debug!("This is a debug message");
trace!("This is a trace message");
println!("Application finished");
}
// Run with: RUST_LOG=debug cargo runuse log::{error, warn, info, debug, trace, Level, LevelFilter};
fn main() {
// Configure logger programmatically
env_logger::builder()
.filter_level(LevelFilter::Debug) // Minimum level to show
.init();
// Each level has a specific use case:
// ERROR: Serious problems that require attention
error!("Failed to connect to database");
error!("Configuration file not found: {}", "config.toml");
// WARN: Potentially harmful situations
warn!("Connection pool is running low");
warn!("Rate limit approaching: {}/{} requests", 95, 100);
// INFO: Important runtime events
info!("Server started on port {}", 8080);
info!("User {} logged in", "alice");
// DEBUG: Detailed diagnostic information
debug!("Processing request: {:?}", "/api/users/42");
debug!("Query execution time: {}ms", 15);
// TRACE: Very detailed diagnostic information
trace!("Entering function process_request");
trace!("Variable x = {}, y = {}", 10, 20);
// Check level at runtime
if log::log_enabled!(Level::Debug) {
let expensive_data = compute_debug_info();
debug!("Debug info: {}", expensive_data);
}
}
fn compute_debug_info() -> String {
// Expensive computation only done if debug logging is enabled
"expensive debug data".to_string()
}use log::{info, debug};
fn main() {
// The env_logger reads RUST_LOG environment variable
// Examples:
// RUST_LOG=info - Show info and above
// RUST_LOG=debug - Show debug and above
// RUST_LOG=my_app=trace - Show trace for my_app module only
// RUST_LOG=my_app::db=debug,my_app::api=info
// RUST_LOG=warn,my_app=debug
env_logger::Builder::from_env(
env_logger::Env::default().default_filter_or("info")
).init();
info!("Application starting");
debug!("Debug information"); // Only shown if RUST_LOG includes debug
}
// Run with:
// cargo run # Uses default (info)
// RUST_LOG=debug cargo run # Shows debug and above
// RUST_LOG=my_app=trace cargo run # Shows trace for my_app
// RUST_LOG=warn cargo run # Shows warn and above
// RUST_LOG=info,my_app::module=debug cargo run # Per-module filteringuse log::{info, warn, error, debug};
fn main() {
env_logger::init();
let username = "alice";
let user_id = 42;
let balance = 1234.56;
let items = vec!["apple", "banana", "cherry"];
// Basic formatting
info!("User logged in");
info!("User {} logged in", username);
info!("User {} (id: {}) logged in", username, user_id);
// Named arguments (clearer for multiple values)
info!("User {user} has balance {balance}", user = username, balance = balance);
info!("Processing user {id} with name {name}", id = user_id, name = username);
// Debug formatting with {:?}
debug!("Items: {:?}", items);
debug!("User struct: {:?}", User { id: user_id, name: username });
// Display formatting with {}
info!("Balance: ${:.2}", balance);
// Binary, octal, hex
debug!("ID in hex: {:#x}", user_id);
debug!("ID in binary: {:b}", user_id);
}
#[derive(Debug)]
struct User {
id: u32,
name: String,
}// src/main.rs
use log::{info, debug};
mod network {
use log::{info, debug, trace};
pub fn connect(host: &str) {
trace!("network::connect called with host={}", host);
debug!("Establishing connection to {}", host);
// ... connection logic
info!("Connected to {}", host);
}
pub fn disconnect() {
info!("Disconnected from server");
}
}
mod database {
use log::{info, debug, trace};
pub fn query(sql: &str) {
trace!("database::query called");
debug!("Executing SQL: {}", sql);
// ... query logic
info!("Query returned 42 rows");
}
}
fn main() {
// Configure different log levels for different modules
env_logger::Builder::new()
// Default to info level
.filter_level(log::LevelFilter::Info)
// Show debug for network module
.filter_module("network", log::LevelFilter::Debug)
// Show trace for database module
.filter_module("database", log::LevelFilter::Trace)
.init();
info!("Application starting");
network::connect("example.com");
database::query("SELECT * FROM users");
network::disconnect();
info!("Application finished");
}
/* Output with above configuration:
INFO [main] Application starting
DEBUG [network] Establishing connection to example.com
INFO [network] Connected to example.com
TRACE [database] database::query called
DEBUG [database] Executing SQL: SELECT * FROM users
INFO [database] Query returned 42 rows
INFO [network] Disconnected from server
INFO [main] Application finished
*/use log::{info, warn, error, LevelFilter};
use std::io::Write;
fn main() {
// Custom formatter
env_logger::builder()
.format(|buf, record| {
writeln!(
buf,
"[{} {} {}] {}",
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.target(),
record.args()
)
})
.filter_level(LevelFilter::Debug)
.init();
info!("Application started");
warn!("Low memory warning");
error!("Failed to open file");
}
// Add to Cargo.toml:
// chrono = "0.4"// src/lib.rs
// Libraries should use the log crate but NOT initialize a logger
// The application using the library will choose the logger implementation
use log::{debug, info, warn, error};
pub struct Cache<K, V> {
data: std::collections::HashMap<K, V>,
capacity: usize,
}
impl<K: std::hash::Hash + Eq + Clone + std::fmt::Debug, V: Clone> Cache<K, V> {
pub fn new(capacity: usize) -> Self {
debug!("Creating cache with capacity {}", capacity);
Cache {
data: std::collections::HashMap::new(),
capacity,
}
}
pub fn get(&self, key: &K) -> Option<&V> {
let result = self.data.get(key);
match &result {
Some(_) => debug!("Cache hit for key {:?}", key),
None => debug!("Cache miss for key {:?}", key),
}
result
}
pub fn insert(&mut self, key: K, value: V) {
if self.data.len() >= self.capacity {
warn!("Cache full, cannot insert {:?}", key);
return;
}
debug!("Inserting key {:?}", key);
self.data.insert(key, value);
}
pub fn clear(&mut self) {
info!("Clearing cache (was {} items)", self.data.len());
self.data.clear();
}
}
// Application code would initialize the logger:
// fn main() {
// env_logger::init();
// let mut cache = Cache::new(10);
// // ...
// }# Cargo.toml
[dependencies]
log = "0.4"
fern = "0.6"
chrono = "0.4"use log::{info, warn, error, LevelFilter};
use fern::colors::{Color, ColoredLevelConfig};
fn setup_logging() -> Result<(), Box<dyn std::error::Error>> {
let colors = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Yellow)
.info(Color::Green)
.debug(Color::Blue)
.trace(Color::BrightBlack);
fern::Dispatch::new()
// Output to stdout with colors
.chain(
fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"[{} {} {}] {}",
chrono::Local::now().format("%H:%M:%S"),
colors.color(record.level()),
record.target(),
message
))
})
.chain(std::io::stdout())
)
// Output to file
.chain(
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
))
})
.chain(fern::log_file("app.log")?)
)
.level(LevelFilter::Debug)
.apply()?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
setup_logging()?;
info!("Application starting");
warn!("This is a warning");
error!("This is an error");
Ok(())
}use log::{info, debug, trace};
fn main() {
env_logger::init();
// log_enabled! macro checks if logging is enabled at compile time
// Useful for avoiding expensive computations when logging is disabled
// Always logged
info!("Application starting");
// Only compute if debug logging is enabled
if log::log_enabled!(log::Level::Debug) {
let stats = compute_expensive_statistics();
debug!("Application stats: {:?}", stats);
}
// Only compute if trace logging is enabled
if log::log_enabled!(log::Level::Trace) {
let state = capture_full_state();
trace!("Full application state: {:?}", state);
}
}
fn compute_expensive_statistics() -> Vec<u64> {
// Simulate expensive computation
(0..1000).collect()
}
fn capture_full_state() -> String {
// Simulate expensive state capture
"Full state...".repeat(100)
}use log::{info, debug, warn, error};
struct UserService {
db_url: String,
cache: std::collections::HashMap<u32, String>,
}
impl UserService {
pub fn new(db_url: &str) -> Self {
info!("Creating UserService with db_url={}", db_url);
UserService {
db_url: db_url.to_string(),
cache: std::collections::HashMap::new(),
}
}
pub fn get_user(&mut self, id: u32) -> Option<&String> {
debug!("Getting user with id={}", id);
// Check cache first
if let Some(user) = self.cache.get(&id) {
debug!("User {} found in cache", id);
return Some(user);
}
// Simulate database lookup
debug!("User {} not in cache, querying database", id);
if id > 0 && id < 100 {
let user = format!("User {}", id);
self.cache.insert(id, user);
info!("User {} loaded from database", id);
self.cache.get(&id)
} else {
warn!("User {} not found", id);
None
}
}
pub fn delete_user(&mut self, id: u32) -> bool {
debug!("Deleting user {}", id);
if self.cache.remove(&id).is_some() {
info!("User {} deleted from cache", id);
true
} else {
warn!("User {} not in cache, cannot delete", id);
false
}
}
}
struct App {
service: UserService,
}
impl App {
pub fn new() -> Self {
info!("Initializing application");
App {
service: UserService::new("postgres://localhost/mydb"),
}
}
pub fn run(&mut self) {
info!("Application running");
// Test user operations
self.service.get_user(1);
self.service.get_user(1); // Cache hit
self.service.get_user(999); // Not found
self.service.delete_user(1);
self.service.delete_user(1); // Already deleted
info!("Application finished");
}
}
fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();
let mut app = App::new();
app.run();
}use log::{info, warn, error, debug};
use std::fs;
use std::io;
fn read_config(path: &str) -> io::Result<String> {
info!("Reading configuration from {}", path);
fs::read_to_string(path).map_err(|e| {
error!("Failed to read config '{}': {}", path, e);
e
})
}
fn parse_config(content: &str) -> Result<Config, String> {
debug!("Parsing configuration ({} bytes)", content.len());
if content.is_empty() {
warn!("Configuration is empty");
return Err("Empty configuration".to_string());
}
// Simulate parsing
Ok(Config {
name: "default".to_string(),
port: 8080,
})
}
#[derive(Debug)]
struct Config {
name: String,
port: u16,
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let content = read_config("config.txt")
.map_err(|e| format!("Config error: {}", e))?;
let config = parse_config(&content)
.map_err(|e| format!("Parse error: {}", e))?;
info!("Configuration loaded: name={}, port={}", config.name, config.port);
Ok(())
}
fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();
match run() {
Ok(()) => info!("Application completed successfully"),
Err(e) => error!("Application failed: {}", e),
}
}log crate for logging macros in libraries and applicationsenv_logger or another implementation for actual log outputerror!, warn!, info!, debug!, trace!main() with env_logger::init()RUST_LOG environment variableRUST_LOG=debug shows debug and aboveRUST_LOG=my_module=trace sets level for specific modulelog_enabled!(Level::Debug) to avoid expensive computations{} for Display, {:?} for Debuginfo!("User {id} logged in", id = 42).filter_module("module", level) for per-module filteringfern for advanced features like file output and rotation