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 output
  • RUST_LOG=debug environment variable has no effect
  • Spans created with tracing::span! do not appear in output
  • Application runs successfully but no logs anywhere
  • log::info! works but tracing::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_subscriber not initialized in main() or #[tokio::main]
  • EnvFilter not configured, defaulting to ERROR level only
  • Missing tracing feature on dependency libraries
  • log crate and tracing not bridged
  • Subscriber initialized in library code, not binary crate

Step-by-Step Fix

  1. 1.Initialize tracing subscriber in main function:
  2. 2.```rust
  3. 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. 1.Configure filter for specific modules:
  2. 2.```rust
  3. 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. 1.Bridge log crate to tracing for dependencies using log!:
  2. 2.```rust
  3. 3.// Cargo.toml
  4. 4.[dependencies]
  5. 5.tracing = "0.1"
  6. 6.tracing-subscriber = { version = "0.3", features = ["env-filter"] }
  7. 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. 1.Layer multiple subscribers for different outputs:
  2. 2.```rust
  3. 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. 1.**Debug why tracing output is missing":
  2. 2.```rust
  3. 3.// Add this at startup to verify subscriber setup
  4. 4.fn debug_tracing_setup() {
  5. 5.// Test basic tracing
  6. 6.tracing::info!("Test info message");
  7. 7.tracing::debug!("Test debug message");
  8. 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_LOG in development and staging environments
  • Add a startup test that verifies tracing output
  • Use tracing-appender for file output in production
  • Document the required tracing setup for new team members