Introduction
The tracing ecosystem is Rust's modern observability framework, but it requires explicit subscriber initialization. If no subscriber is set as the global default, all tracing::info!, tracing::debug!, and span events are silently dropped. Even with a subscriber configured, incorrect filter levels, missing tracing-subscriber features, or log crate bridge issues can prevent output from appearing.
Symptoms
tracing::info!("starting up")produces no outputRUST_LOG=debugenvironment variable has no effect- Spans created with
tracing::span!do not appear in output - Application runs successfully but no logs anywhere
log::info!works buttracing::info!does not
Verify subscriber is set:
``rust
// Check if a subscriber is already set
let subscriber_set = tracing::subscriber::try_set_default(
tracing_subscriber::FmtSubscriber::default()
);
if subscriber_set.is_err() {
eprintln!("WARNING: A subscriber was already set, ignoring this one");
}
Common Causes
tracing_subscribernot initialized inmain()or#[tokio::main]EnvFilternot configured, defaulting to ERROR level only- Missing
tracingfeature on dependency libraries logcrate andtracingnot bridged- Subscriber initialized in library code, not binary crate
Step-by-Step Fix
- 1.Initialize tracing subscriber in main function:
- 2.```rust
- 3.use tracing_subscriber::{fmt, EnvFilter};
#[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // Initialize subscriber BEFORE any tracing calls let filter = EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::fmt() .with_env_filter(filter) .with_target(true) .with_thread_ids(true) .with_file(true) .with_line_number(true) .init();
// Now tracing calls will produce output tracing::info!("Application starting up"); tracing::debug!(port = 3000, "Server configuration loaded");
Ok(()) } ```
- 1.Configure filter for specific modules:
- 2.```rust
- 3.use tracing_subscriber::EnvFilter;
// Set filter levels per module let filter = EnvFilter::try_new( "info,\ my_crate::handler=debug,\ my_crate::database=trace,\ hyper=warn,\ sqlx=debug" ).expect("Invalid filter configuration");
tracing_subscriber::fmt() .with_env_filter(filter) .init(); ```
- 1.Bridge log crate to tracing for dependencies using log!:
- 2.```rust
- 3.// Cargo.toml
- 4.[dependencies]
- 5.tracing = "0.1"
- 6.tracing-subscriber = { version = "0.3", features = ["env-filter"] }
- 7.tracing-log = "0.2"
// main.rs fn init_tracing() { // Bridge log crate to tracing tracing_log::LogTracer::init().expect("Failed to init log tracer");
tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) // Show which library emitted the log .with_target(true) .init(); } ```
- 1.Layer multiple subscribers for different outputs:
- 2.```rust
- 3.use tracing_subscriber::{fmt, layer::SubscriberExt, Registry, EnvFilter};
fn init_layered_subscribers() { // Console output - human readable let console_layer = fmt::layer() .with_target(true) .with_thread_ids(true);
// JSON file output - for log aggregation let file_appender = tracing_appender::rolling::daily("./logs", "app.log"); let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
let json_file_layer = fmt::layer() .with_writer(non_blocking) .json();
// Combine layers with shared filter let filter = EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("info"));
let subscriber = Registry::default() .with(filter) .with(console_layer) .with(json_file_layer);
tracing::subscriber::set_global_default(subscriber) .expect("Failed to set subscriber"); } ```
- 1.**Debug why tracing output is missing":
- 2.```rust
- 3.// Add this at startup to verify subscriber setup
- 4.fn debug_tracing_setup() {
- 5.// Test basic tracing
- 6.tracing::info!("Test info message");
- 7.tracing::debug!("Test debug message");
- 8.tracing::warn!("Test warn message");
// Test spans let span = tracing::info_span!("test_span"); let _enter = span.enter(); tracing::info!("Inside span");
// Check current subscriber if tracing::subscriber::get_default().is_some() { eprintln!("Subscriber is set"); } else { eprintln!("WARNING: No subscriber set - tracing output will be lost"); }
// Check RUST_LOG if let Ok(rust_log) = std::env::var("RUST_LOG") { eprintln!("RUST_LOG is set to: {}", rust_log); } else { eprintln!("RUST_LOG is not set, using default filter"); } } ```
Prevention
- Always initialize subscriber as the very first thing in
main() - Use
tracing_subscriber::fmt().init()as a minimum default - Set
RUST_LOGin development and staging environments - Add a startup test that verifies tracing output
- Use
tracing-appenderfor file output in production - Document the required tracing setup for new team members