dcontext-tracing
Automatic dcontext scope management via tracing spans.
When you enter a tracing span, this crate automatically creates a dcontext scope. When the span exits, the scope is reverted. This means your context values follow the natural span lifecycle — no manual scope management needed.
Quick Start
[]
= "0.3"
= "0.3"
= "0.1"
= "0.3"
use *;
// Zero-config: every span creates a dcontext scope
registry
.with
.init;
Three Levels of Integration
Level 1: Automatic Scoping (Zero Config)
Every span enter creates a new dcontext scope that inherits the parent scope's values. When the span exits, changes are reverted:
set_context;
// Scope reverted — "request_id" is gone, "user" remains
Level 2: Field-to-Context Mapping
Map tracing span fields directly to dcontext values. When a span with the configured field is entered, the value is extracted and set in context:
use ;
;
let layer = builder
.
.build;
registry.with.init;
// Now this span automatically sets RequestId in dcontext:
let _span = info_span!.entered;
let id: RequestId = get_context;
assert_eq!;
You can also map a field to a different context key name:
let layer = builder
.
.build;
Level 3: Span Info
Expose span metadata (name, target, level) as a context value:
use ;
let layer = builder
.include_span_info
.build;
registry.with.init;
Combining All Levels
let layer = builder
.
.
.include_span_info
.build;
Implementing FromFieldValue
The FromFieldValue trait converts tracing field values to your context types.
Implement the conversion methods that match your field's type:
use FromFieldValue;
// String field → context type
;
// Numeric field → context type
;
// Boolean field → context type
;
Async Behavior
When used with Instrument,
the layer creates and reverts a scope around each poll of the future. Mapped
field values and span info are re-applied on each enter, so reads via
force_thread_local() will see the correct values during each poll:
use Instrument;
async
handle_request
.instrument
.await;
Note: Mutations made inside a span do not persist across
.awaitpoints — each poll gets a fresh scope. For full async context propagation across.await, usedcontext::with_context()ordcontext::ContextFuturedirectly.
Full Example
See samples/src/bin/tracing_scopes.rs
for a complete working example.
API Reference
| Type | Purpose |
|---|---|
DcontextLayer<S> |
Tracing layer — creates dcontext scopes on span enter/exit |
DcontextLayerBuilder<S> |
Builder for configuring the layer |
FromFieldValue |
Trait for converting tracing fields to context types |
SpanInfo |
Span metadata (name, target, level) |
SPAN_INFO_KEY |
Context key for SpanInfo ("dcontext.span") |
How It Works
The layer uses a thread-local stack to store dcontext ScopeGuards (which
are !Send and cannot be stored in tracing's span extensions). On span enter,
a new scope is pushed; on span exit, the scope is popped and the guard dropped,
reverting context changes. This mirrors the approach used by
tracing-opentelemetry.
All dcontext operations in callbacks use force_thread_local() to ensure
correct behavior inside tokio runtimes.
Scope Chain Integration
Span names automatically become named scopes in the dcontext scope chain.
Each time a span is entered, the layer calls enter_named_scope(span_name),
so scope_chain() reflects the tracing span hierarchy:
let _outer = info_span!.entered;
// After _inner exits: chain == ["api_handler"]
This works transparently — no extra configuration needed beyond installing
the DcontextLayer.
Thread-local limitation: Because
dcontext-tracingusesforce_thread_local(), the scope chain reflects thread-local state. In async code withInstrument, the chain is correct during eachpoll()but may not persist across.awaitpoints. For full async chain propagation, combine withdcontext::with_context()ordcontext::named_scope_async().
Related
- dcontext — Core context propagation library
- dcontext-dactor — dactor actor framework integration
- Usage Guide
License
MIT