Loading page…
Rust walkthroughs
Loading page…
tracing::span::Span::entered and in_scope for span activation?tracing::span::Span::entered and in_scope both activate a span for the duration of their scope, but they differ fundamentally in how they manage the tracing context: entered returns an Entered guard that keeps the span active until the guard is dropped, allowing the span to persist across await points in async code, while in_scope takes a closure and activates the span only for the duration of that closure, making it impossible for the span to leak across async boundaries or to code that shouldn't be traced. The choice between them is a trade-off between flexibility (entered can span multiple statements and await points) and safety (in_scope has bounded, deterministic scope that cannot accidentally persist).
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "operation");
// entered returns a guard that keeps span active until dropped
let _enter = span.entered();
// All events here are within the span
tracing::info!("This is inside the span");
// More code can be added while span is active
do_work();
// Span is exited when _enter goes out of scope
}
fn do_work() {
tracing::info!("Also inside the span");
}entered returns an Entered guard that maintains the span context until dropped.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "operation");
// in_scope takes a closure, span active only during closure execution
span.in_scope(|| {
tracing::info!("Inside the span");
do_work();
});
// Span is no longer active here
tracing::info!("Outside the span");
}
fn do_work() {
tracing::info!("Still inside the span");
}in_scope confines span activation to the closure body, exiting immediately after.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "operation");
// entered: you control when the guard drops
{
let _enter = span.entered();
tracing::info!("Inside span");
// Span active here
}
// Span exited after this block
// Can also drop explicitly
let _enter2 = span.entered();
tracing::info!("Inside second span");
drop(_enter2); // Explicit exit
tracing::info!("Outside span");
// in_scope: scope is always bounded to closure
span.in_scope(|| {
tracing::info!("Inside in_scope");
// Can't accidentally leak beyond closure
});
// Definitely outside span here
}entered gives explicit control over guard lifetime; in_scope guarantees bounded scope.
use tracing::{span, Level};
#[tokio::main]
async fn main() {
let span = span!(Level::INFO, "async_operation");
// entered can span await points
let _enter = span.entered();
tracing::info!("Before await");
// Span context persists across await
some_async_work().await;
tracing::info!("After await");
// Still in span context
more_async_work().await;
// Span exits when _enter drops at end of function
}
async fn some_async_work() {
tracing::info!("Inside async function, still in span");
}
async fn more_async_work() {
tracing::info!("Also in span");
}entered guards survive across await points, keeping the span active in async contexts.
use tracing::{span, Level};
#[tokio::main]
async fn main() {
let span = span!(Level::INFO, "operation");
// in_scope with sync closure
span.in_scope(|| {
tracing::info!("Synchronous code in span");
});
// This won't compile - can't use async closure
// span.in_scope(async || {
// tracing::info!("Won't compile");
// });
// For async, you need entered
{
let _enter = span.entered();
async_work().await;
}
}
async fn async_work() {
tracing::info!("Async work");
}in_scope cannot be used with async code directly; entered is required for spanning await points.
use tracing::{span, Level};
fn process_data() {
let span = span!(Level::INFO, "processing");
// entered: span context propagates to called functions
let _enter = span.entered();
validate_data();
transform_data();
save_data();
// All three functions run within the span
}
fn validate_data() {
tracing::info!("Validating"); // Inside span
}
fn transform_data() {
tracing::info!("Transforming"); // Inside span
}
fn save_data() {
tracing::info!("Saving"); // Inside span
}
fn main() {
process_data();
}Both entered and in_scope propagate context to called functions within their scope.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "safe_operation");
// in_scope prevents accidental scope extension
span.in_scope(|| {
tracing::info!("Inside span");
// Can't accidentally return the guard
// Can't accidentally leak to other code
// Scope is strictly bounded
});
// Definitely outside span
tracing::info!("Outside span");
// entered allows more flexibility
let _enter = span.entered();
if some_condition() {
// Span active here
tracing::info!("In if block");
// Early return? Guard still valid
return;
}
tracing::info!("After if, still in span");
// Guard drops at end of scope
}
fn some_condition() -> bool {
false
}in_scope enforces bounded scope; entered requires discipline to manage guard lifetime.
use tracing::{span, Level};
fn main() {
let outer = span!(Level::INFO, "outer");
let inner = span!(Level::INFO, "inner");
// Using entered
let _outer = outer.entered();
tracing::info!("In outer span");
{
let _inner = inner.entered();
tracing::info!("In inner span (nested)");
}
tracing::info!("Back to outer span");
// Using in_scope
outer.in_scope(|| {
tracing::info!("In outer (in_scope)");
inner.in_scope(|| {
tracing::info!("In inner (nested in_scope)");
});
tracing::info!("Back to outer");
});
}Both approaches handle nested spans correctly; context stack is managed properly.
use tracing::{span, Level};
use std::thread;
fn main() {
let span = span!(Level::INFO, "thread_example");
// entered guard is Send, can be moved to another thread
let _enter = span.entered();
// But span context is thread-local
// The guard affects only the current thread's context
thread::spawn(move || {
// _enter moved here, but context doesn't transfer
tracing::info!("In spawned thread");
// This is NOT inside the span context
});
tracing::info!("In main thread, in span");
}
fn correct_threading() {
let span = span!(Level::INFO, "correct_threading");
// Use in_scope in each thread
thread::spawn(move || {
span.in_scope(|| {
tracing::info!("In spawned thread, in span");
});
});
}Span context is thread-local; both entered and in_scope affect only the current thread.
use tracing::{span, Level};
fn process_with_early_exit(input: &str) -> Option<String> {
let span = span!(Level::INFO, "process");
// entered: works with early returns
let _enter = span.entered();
if input.is_empty() {
tracing::info!("Empty input");
return None; // Guard drops, span exits
}
tracing::info!("Processing: {}", input);
Some(input.to_uppercase())
}
fn process_with_in_scope(input: &str) -> Option<String> {
let span = span!(Level::INFO, "process");
// in_scope: must handle return through closure
span.in_scope(|| {
if input.is_empty() {
tracing::info!("Empty input");
return None; // Returns from closure
}
tracing::info!("Processing: {}", input);
Some(input.to_uppercase())
})
}
fn main() {
println!("{:?}", process_with_early_exit(""));
println!("{:?}", process_with_early_exit("hello"));
println!("{:?}", process_with_in_scope(""));
println!("{:?}", process_with_in_scope("hello"));
}Both handle early returns; in_scope requires thinking about closure semantics.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "operation");
// entered: one-time cost for entering
let _enter = span.entered();
// Multiple operations under span
for i in 0..1000 {
tracing::info!("Iteration {}", i);
}
// in_scope: enters and exits for each call
// More expensive if called in a loop
for i in 0..1000 {
span.in_scope(|| {
tracing::info!("Iteration {}", i);
});
}
// Better: single in_scope for the loop
span.in_scope(|| {
for i in 0..1000 {
tracing::info!("Iteration {}", i);
}
});
}Repeated in_scope calls have overhead; entered is more efficient for extended spans.
use tracing::{span, Level};
struct SpanGuard {
_enter: tracing::span::Entered,
}
impl SpanGuard {
fn new(span: &tracing::Span) -> Self {
Self {
_enter: span.entered(),
}
}
}
fn main() {
let span = span!(Level::INFO, "raii_example");
{
let _guard = SpanGuard::new(&span);
tracing::info!("Inside RAII guard");
// Guard automatically exits when dropped
// Even on panic (RAII guarantees)
}
tracing::info!("Outside span");
}entered integrates naturally with RAII patterns; the guard's lifetime is deterministic.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "conditional");
let should_trace = std::env::var("TRACE").is_ok();
// entered: can conditionally activate
let _enter = if should_trace {
Some(span.entered())
} else {
None
};
tracing::info!("This may or may not be traced");
// in_scope: more awkward for conditional use
if should_trace {
span.in_scope(|| {
tracing::info!("Traced");
});
} else {
tracing::info!("Not traced");
}
}entered integrates naturally with conditional patterns via Option<Entered>.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "cloned");
// entered: each activation needs its own guard
let _enter1 = span.entered();
tracing::info!("First activation");
// Can't enter twice on same span with same guard
// let _enter2 = span.entered(); // Would panic
// Clone the span for separate activation
let span2 = span.clone();
let _enter2 = span2.entered();
tracing::info!("Second activation (nested)");
// in_scope can be called multiple times on same span
span.in_scope(|| {
tracing::info!("First in_scope");
});
span.in_scope(|| {
tracing::info!("Second in_scope");
});
}in_scope can be called repeatedly; entered requires careful handling for re-entry.
use tracing::{span, Level};
fn fallible_operation() -> Result<String, &'static str> {
let span = span!(Level::INFO, "fallible");
// entered: guard ensures cleanup even on error
let _enter = span.entered();
tracing::info!("Starting operation");
// If this returns Err, guard still drops
Err("Something went wrong")
}
fn fallible_in_scope() -> Result<String, &'static str> {
let span = span!(Level::INFO, "fallible");
// in_scope: closure result propagates naturally
span.in_scope(|| -> Result<String, &'static str> {
tracing::info!("Starting operation");
Err("Something went wrong")
})
}
fn main() {
match fallible_operation() {
Ok(s) => println!("Success: {}", s),
Err(e) => println!("Error: {}", e),
}
match fallible_in_scope() {
Ok(s) => println!("Success: {}", s),
Err(e) => println!("Error: {}", e),
}
}Both handle errors correctly; in_scope propagates closure return values naturally.
Method comparison:
| Aspect | entered | in_scope |
|--------|-----------|------------|
| Returns | Entered guard | Closure's return value |
| Scope | Until guard drops | Closure body only |
| Async support | Yes (spans await) | No (sync only) |
| Re-entry | Clone needed | Can call repeatedly |
| Conditional use | Option<Entered> | Awkward |
| Control | Explicit guard lifetime | Bounded by closure |
When to use entered:
| Scenario | Reason |
|----------|--------|
| Async code | Spans await points |
| Multiple statements | No closure needed |
| Conditional activation | Option<Entered> pattern |
| RAII patterns | Guard integrates with ownership |
| Long-running spans | Single enter cost |
When to use in_scope:
| Scenario | Reason | |----------|--------| | Bounded operations | Closure guarantees exit | | Sync code | Natural closure semantics | | Nested with control | Prevents scope extension | | Return value needed | Closure returns directly | | Single expression | Concise syntax |
Key insight: entered and in_scope represent different philosophies of span management: entered provides an Entered guard that gives explicit control over span lifetime, allowing it to span multiple statements, conditionals, and await points—flexibility that requires discipline to manage correctly. in_scope takes a closure and guarantees the span is active only during that closure's execution, providing bounded scope that cannot accidentally leak—safety that comes at the cost of flexibility, particularly in async code where the closure cannot span await points. Use entered for async code, long-running spans, and RAII patterns where you want explicit control; use in_scope for synchronous bounded operations, single expressions, and code where you want the span scope to be structurally guaranteed by the function boundary.