Skip to main content

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 Mapping
47//!
48//! Map tracing span fields directly to dcontext values:
49//!
50//! ```rust,no_run
51//! use dcontext_tracing::{DcontextLayer, FromFieldValue};
52//! use tracing_subscriber::Registry;
53//!
54//! #[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
55//! struct RequestId(String);
56//!
57//! impl FromFieldValue for RequestId {
58//!     fn from_str_value(s: &str) -> Option<Self> {
59//!         Some(RequestId(s.to_string()))
60//!     }
61//! }
62//!
63//! let layer: DcontextLayer<Registry> = DcontextLayer::builder()
64//!     .map_field::<RequestId>("request_id")
65//!     .build();
66//! ```
67//!
68//! ### Level 3: Span Info
69//!
70//! Expose span metadata as a context value:
71//!
72//! ```rust,no_run
73//! use dcontext_tracing::{DcontextLayer, SpanInfo};
74//! use tracing_subscriber::Registry;
75//!
76//! let layer: DcontextLayer<Registry> = DcontextLayer::builder()
77//!     .include_span_info()
78//!     .build();
79//!
80//! // Inside a span:
81//! // let info: SpanInfo = dcontext::get_context("dcontext.span");
82//! // info.name, info.target, info.level
83//! ```
84//!
85//! ## How It Works
86//!
87//! The layer uses a thread-local stack to store dcontext `ScopeGuard`s
88//! (which are `!Send` and cannot be stored in tracing's span extensions).
89//! On span enter, a new scope is pushed; on span exit, the scope is popped
90//! and the guard dropped, reverting context changes made in that scope.
91//!
92//! This mirrors the approach used by `tracing-opentelemetry` for similar
93//! thread-local guard management.
94//!
95//! ## Async Behavior
96//!
97//! When used with [`Instrument`](tracing::Instrument), the layer creates and
98//! reverts a scope around each poll of the future. Mapped field values and span
99//! info are re-applied on each enter, so reads via `force_thread_local()` will
100//! see the correct values during each poll. However, **mutations made inside a
101//! span do not persist across `.await` points** — each poll gets a fresh scope.
102//!
103//! For full async context propagation across `.await`, use `dcontext::with_context()`
104//! or `dcontext::ContextFuture` directly.
105
106mod field_mapping;
107mod guard_stack;
108mod layer;
109mod span_info;
110
111#[cfg(test)]
112mod tests;
113
114pub use field_mapping::FromFieldValue;
115pub use layer::{DcontextLayer, DcontextLayerBuilder};
116pub use span_info::{SpanInfo, SPAN_INFO_KEY};