Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Logging and Tracing: syslog/printf → log + tracing
日志与追踪:从 syslog/printf 到 log + tracing

What you’ll learn: Rust’s two-layer logging architecture (facade + backend), the log and tracing crates, structured logging with spans, and how this replaces printf/syslog debugging.
本章将学到什么: Rust 的双层日志架构,也就是 facade 加 backend;logtracing 这两个核心 crate;带 span 的结构化日志;以及这一整套是怎样替代 printf / syslog 式调试的。

C++ diagnostic code typically uses printf, syslog, or custom logging frameworks. Rust has a standardized two-layer logging architecture: a facade crate (log or tracing) and a backend (the actual logger implementation).
C++ 诊断代码里最常见的是 printfsyslog,或者各写各的日志框架。Rust 这边则已经形成了标准化的双层结构:前面是一层 facade crate,例如 logtracing,后面再挂真正负责输出的 backend

The log facade — Rust’s universal logging API
log facade:Rust 通用日志 API

The log crate provides macros that mirror syslog severity levels. Libraries use log macros; binaries choose a backend:
log crate 提供了一套和 syslog 严重级别非常接近的宏。库通常只写 log 宏,最终具体输出到哪里,由二进制程序决定后端:

// Cargo.toml
// [dependencies]
// log = "0.4"
// env_logger = "0.11"    # One of many backends

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

fn check_sensor(id: u32, temp: f64) {
    trace!("Reading sensor {id}");           // Finest granularity
    debug!("Sensor {id} raw value: {temp}"); // Development-time detail

    if temp > 85.0 {
        warn!("Sensor {id} high temperature: {temp}°C");
    }
    if temp > 95.0 {
        error!("Sensor {id} CRITICAL: {temp}°C — initiating shutdown");
    }
    info!("Sensor {id} check complete");     // Normal operation
}

fn main() {
    // Initialize the backend — typically done once in main()
    env_logger::init();  // Controlled by RUST_LOG env var

    check_sensor(0, 72.5);
    check_sensor(1, 91.0);
}
# Control log level via environment variable
RUST_LOG=debug cargo run          # Show debug and above
RUST_LOG=warn cargo run           # Show only warn and error
RUST_LOG=my_crate=trace cargo run # Per-module filtering
RUST_LOG=my_crate::gpu=debug,warn cargo run  # Mix levels

C++ comparison
和 C++ 的对照

C++Rust (log)Notes
printf("DEBUG: %s\n", msg)
printf("DEBUG: %s\n", msg)
debug!("{msg}")
debug!("{msg}")
Format checked at compile time
格式在编译期就会检查
syslog(LOG_ERR, "...")
syslog(LOG_ERR, "...")
error!("...")
error!("...")
Backend decides where output goes
实际输出目标由后端决定
#ifdef DEBUG around log calls
#ifdef DEBUG 包日志调用
trace! / debug! compiled out at max_level
trace! / debug! 在高优化级别下可被编译期裁掉
Custom Logger::log(level, msg)
自定义 Logger::log(level, msg)
log::info!("...") — all crates use same API
log::info!("..."),全生态共用一套 API
Per-file log verbosity
按文件调日志级别
RUST_LOG=crate::module=level
RUST_LOG=crate::module=level
Environment-based, no recompile
环境变量控制,不需要重编译

The tracing crate — structured logging with spans
tracing crate:带 span 的结构化日志

tracing extends log with structured fields and spans (timed scopes). This is especially useful for diagnostics code where you want to track context:
tracinglog 的基础上继续加了 结构化字段span,也就是带时序范围的上下文。这对诊断代码尤其有价值,因为它天生适合把上下文信息一路带下去。

// Cargo.toml
// [dependencies]
// tracing = "0.1"
// tracing-subscriber = { version = "0.3", features = ["env-filter"] }

use tracing::{info, warn, error, instrument, info_span};

#[instrument(skip(data), fields(gpu_id = gpu_id, data_len = data.len()))]
fn run_gpu_test(gpu_id: u32, data: &[u8]) -> Result<(), String> {
    info!("Starting GPU test");

    let span = info_span!("ecc_check", gpu_id);
    let _guard = span.enter();  // All logs inside this scope include gpu_id

    if data.is_empty() {
        error!(gpu_id, "No test data provided");
        return Err("empty data".to_string());
    }

    // Structured fields — machine-parseable, not just string interpolation
    info!(
        gpu_id,
        temp_celsius = 72.5,
        ecc_errors = 0,
        "ECC check passed"
    );

    Ok(())
}

fn main() {
    // Initialize tracing subscriber
    tracing_subscriber::fmt()
        .with_env_filter("debug")  // Or use RUST_LOG env var
        .with_target(true)          // Show module path
        .with_thread_ids(true)      // Show thread IDs
        .init();

    let _ = run_gpu_test(0, &[1, 2, 3]);
}

Output with tracing-subscriber:
tracing-subscriber 输出时,大概会长这样:

#![allow(unused)]
fn main() {
2026-02-15T10:30:00.123Z DEBUG ThreadId(01) run_gpu_test{gpu_id=0 data_len=3}: my_crate: Starting GPU test
2026-02-15T10:30:00.124Z  INFO ThreadId(01) run_gpu_test{gpu_id=0 data_len=3}:ecc_check{gpu_id=0}: my_crate: ECC check passed gpu_id=0 temp_celsius=72.5 ecc_errors=0
}

#[instrument] — automatic span creation
#[instrument]:自动创建 span

The #[instrument] attribute automatically creates a span with the function name and its arguments:
#[instrument] 这个属性会自动创建一个 span,把函数名和参数都挂进去:

#![allow(unused)]
fn main() {
use tracing::instrument;

#[instrument]
fn parse_sel_record(record_id: u16, sensor_type: u8, data: &[u8]) -> Result<(), String> {
    // Every log inside this function automatically includes:
    // record_id, sensor_type, and data (if Debug)
    tracing::debug!("Parsing SEL record");
    Ok(())
}

// skip: exclude large/sensitive args from the span
// fields: add computed fields
#[instrument(skip(raw_buffer), fields(buf_len = raw_buffer.len()))]
fn decode_ipmi_response(raw_buffer: &[u8]) -> Result<Vec<u8>, String> {
    tracing::trace!("Decoding {} bytes", raw_buffer.len());
    Ok(raw_buffer.to_vec())
}
}

log vs tracing — which to use
logtracing 到底怎么选

Aspectlogtracing
Complexity
复杂度
Simple — 5 macros
简单,核心就是 5 个级别宏
Richer — spans, fields, instruments
更丰富,支持 span、字段和 instrument
Structured data
结构化数据
String interpolation only
基本只能靠字符串插值
Key-value fields: info!(gpu_id = 0, "msg")
原生支持键值字段
Timing / spans
时序 / span
No
没有
Yes — #[instrument], span.enter()
有,#[instrument]span.enter() 都能用
Async support
异步支持
Basic
基础级别
First-class — spans propagate across .await
一等支持,span 能跨 .await 传播
Compatibility
兼容性
Universal facade
通用 facade
Compatible with log (has a log bridge)
兼容 log,也有桥接层
When to use
适用场景
Simple applications, libraries
简单应用、轻量库
Diagnostic tools, async code, observability
诊断工具、异步代码、可观测性系统

Recommendation: Use tracing for production diagnostic-style projects (diagnostic tools with structured output). Use log for simple libraries where you want minimal dependencies. tracing includes a compatibility layer so libraries using log macros still work with a tracing subscriber.
建议:做生产级诊断工具、结构化输出系统,优先上 tracing。如果只是简单库代码,想尽量少依赖,就用 log。另外 tracing 自带兼容层,所以那些还在用 log 宏的库,照样能挂到 tracing subscriber 上工作。

Backend options
可选后端

Backend CrateOutputUse Case
env_logger
env_logger
stderr, colored
stderr,支持彩色输出
Development, simple CLI tools
开发阶段、简单 CLI 工具
tracing-subscriber
tracing-subscriber
stderr, formatted
stderr,格式化输出
Production with tracing
基于 tracing 的生产输出
syslog
syslog
System syslog
系统 syslog
Linux system services
Linux 系统服务
tracing-journald
tracing-journald
systemd journal
systemd journal
systemd-managed services
由 systemd 托管的服务
tracing-appender
tracing-appender
Rotating log files
滚动日志文件
Long-running daemons
长期运行的守护进程
tracing-opentelemetry
tracing-opentelemetry
OpenTelemetry collector
OpenTelemetry 收集器
Distributed tracing
分布式追踪