Loading page…
Rust walkthroughs
Loading page…
tracing::dispatcher::set_global_default affect nested span contexts across async boundaries?tracing::dispatcher::set_global_default establishes a global dispatcher that receives all span and event data for the entire process, and this dispatcher is automatically propagated across thread and async task boundaries through the tracing crate's context tracking. When a span is created in one async task and another task enters that span's context, the global dispatcher ensures both operations are correlated: the span ID and metadata are preserved, allowing the downstream subscriber to reconstruct the full span hierarchy regardless of where the execution context moves. This propagation works because tracing stores the current span context in thread-local storage, and async runtimes that support tracing automatically transfer this context when tasks migrate between threads.
use tracing::{info, span, Level};
use tracing_subscriber::{layer::SubscriberExt, Registry};
fn main() {
// Create a subscriber with layers
let subscriber = Registry::default()
.with(tracing_subscriber::fmt::layer());
// Set as global default
tracing::dispatcher::set_global_default(tracing::Dispatch::new(subscriber))
.expect("Failed to set global default");
// Now all spans and events go through this dispatcher
let span = span!(Level::INFO, "main_operation");
let _enter = span.enter();
info!("This event is dispatched to the global subscriber");
// The dispatcher is now the process-wide default
// No need to pass it around explicitly
}The global dispatcher handles all tracing calls after set_global_default is invoked.
use tracing::{dispatcher, info, span, Level};
fn main() {
// Before setting global default: no dispatcher
// Events are simply dropped
// Create and set global default
let subscriber = tracing_subscriber::fmt()
.with_target(false)
.finish();
dispatcher::set_global_default(dispatcher::Dispatch::new(subscriber))
.unwrap();
// The Dispatch type wraps any Subscriber
// It's Send + Sync, so it can be shared across threads
// How it works:
// 1. set_global_default stores the Dispatch in a static
// 2. All macros (info!, span!, etc.) access this static
// 3. The dispatch forwards to the underlying subscriber
let span = span!(Level::INFO, "operation");
let _guard = span.enter();
info!("Dispatched through global");
// The span context is stored in thread-local storage
// Current span ID and metadata are tracked per-thread
}Dispatch wraps a Subscriber and makes it globally accessible.
use tracing::{info, span, Level};
fn main() {
tracing_subscriber::fmt()
.with_target(false)
.with_thread_ids(true)
.init();
// Create nested spans
let parent = span!(Level::INFO, "parent");
let _parent_guard = parent.enter();
info!("Inside parent span");
{
// Child span - nested within parent
let child = span!(Level::INFO, "child");
let _child_guard = child.enter();
info!("Inside child span");
// Context: parent -> child
// Another level of nesting
let grandchild = span!(Level::INFO, "grandchild");
let _gc_guard = grandchild.enter();
info!("Inside grandchild span");
// Context: parent -> child -> grandchild
}
info!("Back in parent span");
// Context: parent
}Nested spans form a hierarchy that the dispatcher tracks.
use tracing::{info, span, Level};
use std::thread;
fn main() {
tracing_subscriber::fmt()
.with_thread_ids(true)
.with_target(false)
.init();
// Each thread has its own current span context
let parent = span!(Level::INFO, "parent");
thread::spawn(move || {
let _guard = parent.enter();
info!("In spawned thread, inside parent span");
// The span context was NOT automatically transferred
// We had to manually enter the span
});
// This thread has no current span
info!("Main thread, no span context");
thread::spawn(|| {
// No span context from main thread
info!("Another spawned thread, no parent context");
});
std::thread::sleep(std::time::Duration::from_millis(100));
}Each thread has independent span context; spans don't automatically cross threads.
use tracing::{info, span, Level};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_thread_ids(true)
.with_target(false)
.init();
let parent = span!(Level::INFO, "parent");
let _guard = parent.enter();
// Spawn an async task
let handle = tokio::spawn(async {
// Tokio's tracing support propagates the current span context
info!("Inside spawned task");
// Context: parent (propagated from spawn site)
let child = span!(Level::INFO, "child_in_task");
let _child_guard = child.enter();
info!("Inside child span in task");
// Context: parent -> child_in_task
});
handle.await.unwrap();
info!("Back in main");
// Context: parent
}tokio propagates tracing context across async boundaries automatically.
use tracing::{info, span, Level};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_thread_ids(true)
.with_target(false)
.init();
let root = span!(Level::INFO, "root");
let _guard = root.enter();
// Context: root
info!("Starting root span");
// First async block
let task1 = async {
info!("In task1");
// Context: root (propagated)
let task1_child = span!(Level::INFO, "task1_child");
let _guard = task1_child.enter();
info!("In task1 child span");
// Context: root -> task1_child
};
// Second async block
let task2 = async {
info!("In task2");
// Context: root (propagated)
let task2_child = span!(Level::INFO, "task2_child");
let _guard = task2_child.enter();
info!("In task2 child span");
// Context: root -> task2_child
};
// Both tasks see the root span context
tokio::join!(task1, task2);
info!("After tasks");
// Context: root
}Both tasks inherit the root span; each can create its own nested spans.
use tracing::{dispatcher, Dispatch, info, span, Level};
fn main() {
tracing_subscriber::fmt().init();
// Get the current dispatcher
dispatcher::get_default(|dispatch: &Dispatch| {
// Access the global dispatcher
println!("Got dispatcher: {:?}", dispatch);
});
// The dispatcher is accessible from anywhere
// No need to pass it around
let span = span!(Level::INFO, "operation");
let _guard = span.enter();
// Within a span context, can also access current span
dispatcher::get_default(|dispatch| {
// The dispatch has access to current span info
// through its subscriber
});
info!("Event uses global dispatcher");
}get_default provides access to the global dispatcher from anywhere.
use tracing::{dispatcher, Dispatch, info, span, Level};
fn main() {
let subscriber = tracing_subscriber::fmt()
.with_target(false)
.finish();
let dispatch = Dispatch::new(subscriber);
// Use with_global for temporary dispatcher
// (would fail if global already set)
// Instead, use scoped dispatch for temporary override
let alt_subscriber = tracing_subscriber::fmt()
.with_target(true)
.with_thread_names(true)
.finish();
let alt_dispatch = Dispatch::new(alt_subscriber);
// Set global default first
dispatcher::set_global_default(
Dispatch::new(tracing_subscriber::fmt().finish())
).ok(); // May already be set
// For different dispatchers per scope, use tracing-futures
// or manually manage contexts
}set_global_default can only be called once; use alternative approaches for multiple dispatchers.
use tracing::{info, span, Level};
use tracing_subscriber::{layer::SubscriberExt, Registry, Layer};
fn main() {
// Instead of multiple dispatchers, use layered subscribers
// Layer 1: Console output
let console_layer = tracing_subscriber::fmt::layer()
.with_target(false);
// Layer 2: File output (hypothetical)
// let file_layer = ...;
// Combine layers
let subscriber = Registry::default()
.with(console_layer);
// .with(file_layer);
tracing::dispatcher::set_global_default(tracing::Dispatch::new(subscriber))
.unwrap();
// All spans/events go to both layers
let span = span!(Level::INFO, "operation");
let _guard = span.enter();
info!("Event goes to all layers");
}Use layers for multiple outputs instead of multiple dispatchers.
use tracing::{info, span, Level, Instrument};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_thread_ids(true)
.with_target(false)
.init();
// Create a span to instrument an async task
let span = span!(Level::INFO, "api_request",
endpoint = "/users",
request_id = "abc123"
);
// Instrument wraps the future to enter/exit span on poll
async fn handle_request() {
info!("Processing request");
// Simulate async work
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
info!("Request processed");
}
// Instrument attaches span context to the future
handle_request()
.instrument(span)
.await;
// This ensures:
// 1. Span is entered when future is polled
// 2. Context propagates across await points
// 3. Works even if task moves between threads
}Instrument attaches span context to async futures for proper propagation.
use tracing::{info, span, Level, Instrument};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_thread_ids(true)
.with_target(false)
.init();
let parent = span!(Level::INFO, "parent", id = 1);
async {
info!("Inside parent, before await");
// Context: parent
// Nested child span
let child = span!(Level::INFO, "child", id = 2);
let _guard = child.enter();
info!("Inside child, before await");
// Context: parent -> child
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
// After await, context is restored
info!("Inside child, after await");
// Context: parent -> child (still valid!)
// This works because:
// 1. Tokio's tracing support saves context across await
// 2. Global dispatcher is Send + Sync
// 3. Span IDs are globally unique and tracked
}
.instrument(parent)
.await;
}Span context persists across .await points within instrumented futures.
use tracing::{info, span, Level, Instrument};
use tokio::runtime::Runtime;
fn main() {
tracing_subscriber::fmt()
.with_thread_ids(true)
.with_target(false)
.init();
let rt = Runtime::new().unwrap();
rt.block_on(async {
let span = span!(Level::INFO, "migrating_task");
async {
info!("Task started on thread");
// Yield to allow migration to another thread
tokio::task::yield_now().await;
// May now be on a different thread!
info!("Task continued after yield");
// Span context is maintained even if thread changed
// The tracing context is stored per-task, not per-thread
}
.instrument(span)
.await;
});
}Context propagates even when async tasks migrate between threads.
use tracing::{info, span, Level};
fn main() {
tracing_subscriber::fmt()
.with_thread_ids(true)
.with_target(false)
.init();
// How tracing stores context:
// 1. Global Dispatcher (static)
// - Set by set_global_default
// - Shared across all threads
// - Send + Sync
// 2. Thread-local Current Span (per-thread)
// - Each thread has its own "current span" stack
// - Stored as a stack of span IDs
// - span.enter() pushes, drop pops
// 3. Span Registry (in subscriber)
// - Stores span metadata
// - Spans are reference-counted
// - Allows looking up span by ID
let parent = span!(Level::INFO, "parent");
let _guard = parent.enter();
// Current thread now has parent in its context stack
// When we create child spans:
let child = span!(Level::INFO, "child");
// child knows its parent is parent (via context)
// The span ID links them in the hierarchy
let _child_guard = child.enter();
// Context stack: parent -> child
info!("Event");
// Event carries the full context: parent -> child
}Context is stored per-thread but spans are globally registered.
use tracing::{info, span, Level, Instrument};
use std::sync::Arc;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_thread_ids(true)
.with_target(false)
.init();
// Create a span that will be shared across tasks
let shared_span = span!(Level::INFO, "shared_operation", id = "abc");
// Clone span for use in multiple tasks
let span1 = shared_span.clone();
let span2 = shared_span.clone();
// Task 1 enters the span
let task1 = async move {
let _guard = span1.enter();
info!("Task 1 in shared span");
};
// Task 2 enters the same span
let task2 = async move {
let _guard = span2.enter();
info!("Task 2 in shared span");
};
// Both tasks reference the same span
// But they run independently with their own guards
tokio::join!(task1, task2);
// The span is the same span ID, stored in global registry
// Each thread/task has its own entry in its context stack
}Spans can be cloned and shared; each context has its own entry on the stack.
use tracing::{info, span, Level, Instrument};
// Sync function - use span.enter()
fn sync_work() {
let span = span!(Level::INFO, "sync_work");
let _guard = span.enter();
info!("Doing sync work");
}
// Async function - use .instrument()
async fn async_work() {
info!("Doing async work (already instrumented)");
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
// For sync functions: manually enter span
sync_work();
// For async functions: use Instrument trait
let span = span!(Level::INFO, "async_operation");
async_work()
.instrument(span)
.await;
// Or use #[instrument] attribute
instrumented_function().await;
}
#[tracing::instrument]
async fn instrumented_function() {
info!("Automatically instrumented");
// Span name comes from function name
}Use #[instrument] for automatic instrumentation, or .instrument() for manual.
use tracing::{info, span, Level};
fn main() {
// JSON format shows span hierarchy
tracing_subscriber::fmt()
.json()
.with_target(false)
.with_thread_ids(false)
.init();
let request = span!(Level::INFO, "http_request",
method = "GET",
path = "/api/users"
);
let _req_guard = request.enter();
let db_query = span!(Level::INFO, "db_query",
table = "users",
operation = "select"
);
let _db_guard = db_query.enter();
info!("Executing query");
// JSON output includes:
// {
// "timestamp": "...",
// "level": "INFO",
// "message": "Executing query",
// "spans": [
// {"name": "http_request", "method": "GET", "path": "/api/users"},
// {"name": "db_query", "table": "users", "operation": "select"}
// ]
// }
}JSON output format shows the span hierarchy with all parent spans.
Global dispatcher role:
set_global_default establishes a process-wide subscriberSend + Sync) for concurrent accessContext propagation:
.await pointsNested span mechanics:
span.enter() pushes span onto thread-local stackdrop(guard) pops span from stackAsync boundaries:
.instrument(span) attaches context to futuresKey components:
Dispatch wraps SubscriberCommon patterns:
#[instrument] for automatic function tracing.instrument(span) for manual async tracingKey insight: The global dispatcher is the foundation that enables context to flow across async boundaries. When you call set_global_default, you're establishing a shared sink for all tracing data. The tracing crate then manages the complexity of context propagation: each thread has its own current-span stack stored in thread-local storage, but spans themselves are globally registered with unique IDs. When an async task suspends at .await and later resumes—potentially on a different thread—the runtime's tracing integration transfers the context stack to the new thread's local storage. The span IDs remain valid because they reference the global registry. This design means you can trace operations that span multiple threads without manual context management, as long as you use .instrument() or #[instrument] to attach spans to your async code.