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::sync_ctx::set_context("user", "alice".to_string());
38//! // {
39//! //     let _span = tracing::info_span!("request").entered();
40//! //     // New scope created — inherits parent values
41//! //     dcontext::sync_ctx::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::sync_ctx::get_context("dcontext.span").unwrap_or_default();
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//! Tokio async code should use [`AsyncDcontextLayer`], which stores span state
110//! in `dcontext::async_ctx` task-local storage so mapped values, span info, and
111//! scope chain entries persist across `.await` points in the task.
112//!
113//! [`SyncDcontextLayer`] (and the legacy [`DcontextLayer`] alias) remain useful
114//! for synchronous or explicitly thread-local code. `dcontext::sync_ctx` always uses
115//! thread-local storage directly, so no `force_thread_local()` wrapper is needed.
116
117mod async_layer;
118mod field_mapping;
119pub(crate) mod guard_stack;
120mod layer_common;
121mod span_info;
122mod sync_layer;
123mod tracing_field;
124
125#[cfg(test)]
126mod tests;
127
128pub use async_layer::{AsyncDcontextLayer, AsyncDcontextLayerBuilder};
129pub use sync_layer::{SyncDcontextLayer, SyncDcontextLayerBuilder};
130/// Type alias for backward compatibility — `DcontextLayer` is now `SyncDcontextLayer`.
131pub type DcontextLayer<S> = SyncDcontextLayer<S>;
132/// Type alias for backward compatibility — `DcontextLayerBuilder` is now `SyncDcontextLayerBuilder`.
133pub type DcontextLayerBuilder<S> = SyncDcontextLayerBuilder<S>;
134pub use span_info::{SpanInfo, SPAN_INFO_KEY};
135pub use tracing_field::{collect_log_fields, TracingField, TracingFieldBuilder, WithContextFields};