dcontext_tracing/lib.rs
1//! # dcontext-tracing
2//!
3//! Automatic [dcontext](https://docs.rs/dcontext) scope management via
4//! [tracing](https://docs.rs/tracing) spans.
5//!
6//! This crate provides a [`tracing_subscriber::Layer`] that automatically
7//! creates and manages dcontext scopes when tracing spans are entered and
8//! exited. This means your context values follow the natural span lifecycle
9//! without any manual scope management.
10//!
11//! ## Quick Start
12//!
13//! ```rust,no_run
14//! use tracing_subscriber::prelude::*;
15//!
16//! // Zero-config: every span creates a dcontext scope
17//! tracing_subscriber::registry()
18//! .with(dcontext_tracing::DcontextLayer::new())
19//! .init();
20//! ```
21//!
22//! ## Features
23//!
24//! ### Level 1: Automatic Scoping
25//!
26//! With zero configuration, `DcontextLayer` creates a new dcontext scope
27//! every time a span is entered. Values set inside a span are automatically
28//! cleaned up when the span exits, just like tracing's own span lifecycle.
29//!
30//! ```rust,no_run
31//! # use tracing_subscriber::prelude::*;
32//! # tracing_subscriber::registry()
33//! # .with(dcontext_tracing::DcontextLayer::new())
34//! # .init();
35//! #
36//! // Register context keys, then inside a span:
37//! // dcontext::set_context("user", "alice".to_string());
38//! // {
39//! // let _span = tracing::info_span!("request").entered();
40//! // // New scope created — inherits parent values
41//! // dcontext::set_context("request_id", "abc-123".to_string());
42//! // }
43//! // Scope reverted — "request_id" gone, "user" remains
44//! ```
45//!
46//! ### Level 2: Field-to-Context Extraction
47//!
48//! Extract tracing span fields directly into dcontext values using
49//! [`TracingField`] metadata:
50//!
51//! ```rust,no_run
52//! use dcontext_tracing::{DcontextLayer, TracingField};
53//!
54//! #[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
55//! struct RequestId(String);
56//!
57//! impl std::fmt::Display for RequestId {
58//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59//! write!(f, "{}", self.0)
60//! }
61//! }
62//!
63//! let mut builder = dcontext::RegistryBuilder::new();
64//! builder.register_with::<RequestId>("request_id", |opts| {
65//! opts.with_metadata(
66//! TracingField::builder("request_id")
67//! .extract_from_str(|s| Some(RequestId(s.to_string())))
68//! .enrich_display::<RequestId>() // enables both log + span enrichment
69//! .build(),
70//! )
71//! });
72//!
73//! // DcontextLayer discovers TracingField metadata automatically.
74//! // On span enter:
75//! // - Extracts span fields into context (extract direction)
76//! // - Records context values into pre-declared Empty span fields (span record direction)
77//! // let layer = DcontextLayer::new();
78//! ```
79//!
80//! ### Level 3: Span Info
81//!
82//! Expose span metadata as a context value:
83//!
84//! ```rust,no_run
85//! use dcontext_tracing::{DcontextLayer, SpanInfo};
86//! use tracing_subscriber::Registry;
87//!
88//! let layer: DcontextLayer<Registry> = DcontextLayer::builder()
89//! .include_span_info()
90//! .build();
91//!
92//! // Inside a span:
93//! // let info: SpanInfo = dcontext::get_context("dcontext.span");
94//! // info.name, info.target, info.level
95//! ```
96//!
97//! ## How It Works
98//!
99//! The layer uses a thread-local stack to store dcontext `ScopeGuard`s
100//! (which are `!Send` and cannot be stored in tracing's span extensions).
101//! On span enter, a new scope is pushed; on span exit, the scope is popped
102//! and the guard dropped, reverting context changes made in that scope.
103//!
104//! This mirrors the approach used by `tracing-opentelemetry` for similar
105//! thread-local guard management.
106//!
107//! ## Async Behavior
108//!
109//! When used with [`Instrument`](tracing::Instrument), the layer creates and
110//! reverts a scope around each poll of the future. Mapped field values and span
111//! info are re-applied on each enter, so reads via `force_thread_local()` will
112//! see the correct values during each poll. However, **mutations made inside a
113//! span do not persist across `.await` points** — each poll gets a fresh scope.
114//!
115//! For full async context propagation across `.await`, use `dcontext::with_context()`
116//! or `dcontext::ContextFuture` directly.
117
118mod field_mapping;
119mod guard_stack;
120mod layer;
121mod span_info;
122mod tracing_field;
123
124#[cfg(test)]
125mod tests;
126
127pub use layer::{DcontextLayer, DcontextLayerBuilder};
128pub use tracing_field::{TracingField, TracingFieldBuilder, WithContextFields, collect_log_fields};
129pub use span_info::{SpanInfo, SPAN_INFO_KEY};