Skip to content

Commit 99b934b

Browse files
authored
feat: allow OpenTelemetry context access with SpanRef (#234)
## Motivation Since `OtelData` is not intended to be public, there needs to be a way to access the OpenTelemetry context from other Layers and ensure that the context is created. ## Solution Introduce `get_otel_context` that given a span's `ExtensionsMut` and the `Dispatch` for the layers will get or create the OpenTelemetry context.
1 parent 35be2a5 commit 99b934b

File tree

6 files changed

+592
-3
lines changed

6 files changed

+592
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ opentelemetry = { version = "0.31.0", default-features = false, features = [
2727

2828
tracing = { version = "0.1.35", default-features = false, features = ["std"] }
2929
tracing-core = "0.1.28"
30-
tracing-subscriber = { version = "0.3.0", default-features = false, features = [
30+
tracing-subscriber = { version = "0.3.22", default-features = false, features = [
3131
"registry",
3232
"std",
3333
] }

examples/otel_context.rs

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
//! This example demonstrates how to use `OpenTelemetryContext` from a separate layer
2+
//! to access OpenTelemetry context data from tracing spans.
3+
4+
use opentelemetry::trace::{TraceContextExt, TracerProvider as _};
5+
use opentelemetry_sdk::trace::SdkTracerProvider;
6+
use opentelemetry_stdout as stdout;
7+
use std::sync::{Arc, Mutex, OnceLock};
8+
use tracing::{debug, info, span, warn, Subscriber};
9+
use tracing::{dispatcher::WeakDispatch, level_filters::LevelFilter, Dispatch};
10+
use tracing_opentelemetry::{get_otel_context, layer};
11+
use tracing_subscriber::layer::Context;
12+
use tracing_subscriber::prelude::*;
13+
use tracing_subscriber::registry::LookupSpan;
14+
use tracing_subscriber::Layer;
15+
16+
/// A custom layer that demonstrates how to use OpenTelemetryContext
17+
/// to extract OpenTelemetry contexts from span extensions.
18+
#[derive(Clone, Default)]
19+
struct SpanAnalysisLayer {
20+
/// Store span analysis results for demonstration
21+
analysis_results: Arc<Mutex<Vec<SpanAnalysis>>>,
22+
/// Weak reference to the dispatcher for context extraction
23+
dispatch: Arc<OnceLock<WeakDispatch>>,
24+
}
25+
26+
#[derive(Debug, Clone)]
27+
struct SpanAnalysis {
28+
span_name: String,
29+
trace_id: String,
30+
span_id: String,
31+
is_sampled: bool,
32+
}
33+
34+
impl SpanAnalysisLayer {
35+
fn get_analysis_results(&self) -> Vec<SpanAnalysis> {
36+
self.analysis_results.lock().unwrap().clone()
37+
}
38+
39+
fn analyze_span_context(&self, span_name: &str, otel_context: &opentelemetry::Context) {
40+
let span = otel_context.span();
41+
let span_context = span.span_context();
42+
43+
if span_context.is_valid() {
44+
let analysis = SpanAnalysis {
45+
span_name: span_name.to_string(),
46+
trace_id: format!("{:032x}", span_context.trace_id()),
47+
span_id: format!("{:016x}", span_context.span_id()),
48+
is_sampled: span_context.is_sampled(),
49+
};
50+
51+
println!(
52+
"🔍 Analyzing span '{}': trace_id={}, span_id={}, sampled={}",
53+
analysis.span_name,
54+
analysis.trace_id,
55+
analysis.span_id,
56+
span_context.trace_flags().is_sampled()
57+
);
58+
59+
if let Ok(mut results) = self.analysis_results.lock() {
60+
results.push(analysis);
61+
}
62+
}
63+
}
64+
}
65+
66+
impl<S> Layer<S> for SpanAnalysisLayer
67+
where
68+
S: Subscriber + for<'span> LookupSpan<'span>,
69+
{
70+
fn on_register_dispatch(&self, subscriber: &Dispatch) {
71+
let _ = self.dispatch.set(subscriber.downgrade());
72+
}
73+
74+
fn on_new_span(
75+
&self,
76+
attrs: &tracing::span::Attributes<'_>,
77+
id: &tracing::span::Id,
78+
ctx: Context<'_, S>,
79+
) {
80+
let Some(weak_dispatch) = self.dispatch.get() else {
81+
return;
82+
};
83+
84+
// Get the span reference and extract OpenTelemetry context
85+
if let Some(span_ref) = ctx.span(id) {
86+
// This is the key functionality: using OpenTelemetryContext
87+
// to extract the OpenTelemetry context from span extensions
88+
let mut extensions = span_ref.extensions_mut();
89+
if let Some(dispatch) = weak_dispatch.upgrade() {
90+
if let Some(otel_context) = get_otel_context(&mut extensions, &dispatch) {
91+
self.analyze_span_context(attrs.metadata().name(), &otel_context);
92+
} else {
93+
println!(
94+
"⚠️ Could not extract OpenTelemetry context for span '{}'",
95+
attrs.metadata().name()
96+
);
97+
}
98+
}
99+
}
100+
}
101+
102+
fn on_enter(&self, id: &tracing::span::Id, ctx: Context<'_, S>) {
103+
if let Some(weak_dispatch) = self.dispatch.get() {
104+
if let Some(span_ref) = ctx.span(id) {
105+
let mut extensions = span_ref.extensions_mut();
106+
if let Some(dispatch) = weak_dispatch.upgrade() {
107+
if let Some(otel_context) = get_otel_context(&mut extensions, &dispatch) {
108+
let span = otel_context.span();
109+
let span_context = span.span_context();
110+
if span_context.is_valid() {
111+
println!(
112+
"📍 Entering span with trace_id: {:032x}, span_id: {:016x}",
113+
span_context.trace_id(),
114+
span_context.span_id()
115+
);
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
122+
}
123+
124+
fn setup_tracing() -> (impl Subscriber, SdkTracerProvider, SpanAnalysisLayer) {
125+
// Create OpenTelemetry tracer that outputs to stdout
126+
let provider = SdkTracerProvider::builder()
127+
.with_simple_exporter(stdout::SpanExporter::default())
128+
.build();
129+
let tracer = provider.tracer("span_ref_ext_example");
130+
131+
// Create our custom analysis layer
132+
let analysis_layer = SpanAnalysisLayer::default();
133+
134+
// Build the subscriber with multiple layers:
135+
// 1. OpenTelemetry layer for trace export
136+
// 2. Our custom analysis layer that uses OpenTelemetryContext
137+
// 3. Formatting layer for console output
138+
let subscriber = tracing_subscriber::registry()
139+
.with(layer().with_tracer(tracer).with_filter(LevelFilter::DEBUG))
140+
.with(analysis_layer.clone())
141+
.with(
142+
tracing_subscriber::fmt::layer()
143+
.with_target(false)
144+
.with_filter(LevelFilter::INFO),
145+
);
146+
147+
(subscriber, provider, analysis_layer)
148+
}
149+
150+
fn simulate_application_work() {
151+
// Create a root span for the main application work
152+
let root_span = span!(tracing::Level::INFO, "application_main", version = "1.0.0");
153+
let _root_guard = root_span.enter();
154+
155+
info!("Starting application");
156+
157+
// Simulate some business logic with nested spans
158+
{
159+
let auth_span = span!(tracing::Level::DEBUG, "authenticate_user", user_id = 12345);
160+
let _auth_guard = auth_span.enter();
161+
162+
debug!("Validating user credentials");
163+
164+
// Simulate authentication work
165+
std::thread::sleep(std::time::Duration::from_millis(10));
166+
167+
info!("User authenticated successfully");
168+
}
169+
170+
// Simulate database operations
171+
{
172+
let db_span = span!(
173+
tracing::Level::DEBUG,
174+
"database_query",
175+
query = "SELECT * FROM users",
176+
table = "users"
177+
);
178+
let _db_guard = db_span.enter();
179+
180+
debug!("Executing database query");
181+
182+
// Nested span for connection management
183+
{
184+
let conn_span = span!(tracing::Level::DEBUG, "acquire_connection", pool_size = 10);
185+
let _conn_guard = conn_span.enter();
186+
187+
debug!("Acquiring database connection from pool");
188+
std::thread::sleep(std::time::Duration::from_millis(5));
189+
}
190+
191+
std::thread::sleep(std::time::Duration::from_millis(20));
192+
info!("Database query completed");
193+
}
194+
195+
// Simulate some processing work
196+
{
197+
let process_span = span!(
198+
tracing::Level::DEBUG,
199+
"process_data",
200+
records_count = 150,
201+
batch_size = 50
202+
);
203+
let _process_guard = process_span.enter();
204+
205+
debug!("Processing user data");
206+
207+
for batch in 1..=3 {
208+
let batch_span = span!(
209+
tracing::Level::DEBUG,
210+
"process_batch",
211+
batch_number = batch,
212+
batch_size = 50
213+
);
214+
let _batch_guard = batch_span.enter();
215+
216+
debug!("Processing batch {}", batch);
217+
std::thread::sleep(std::time::Duration::from_millis(8));
218+
}
219+
220+
info!("Data processing completed");
221+
}
222+
223+
warn!("Application work completed");
224+
}
225+
226+
fn main() {
227+
println!(
228+
"🚀 OpenTelemetryContext Example: Extracting OpenTelemetry Contexts from Separate Layer"
229+
);
230+
println!("{}", "=".repeat(80));
231+
232+
// Setup tracing with our custom layer
233+
let (subscriber, provider, analysis_layer) = setup_tracing();
234+
235+
tracing::subscriber::with_default(subscriber, || {
236+
// Simulate application work that generates spans
237+
simulate_application_work();
238+
});
239+
240+
// Ensure all spans are flushed
241+
drop(provider);
242+
243+
// Display the analysis results
244+
println!("\n📊 Span Analysis Results:");
245+
println!("{}", "-".repeat(80));
246+
247+
let results = analysis_layer.get_analysis_results();
248+
for (i, analysis) in results.iter().enumerate() {
249+
println!(
250+
"{}. Span: '{}'\n Trace ID: {}\n Span ID: {}\n Sampled: {}\n",
251+
i + 1,
252+
analysis.span_name,
253+
analysis.trace_id,
254+
analysis.span_id,
255+
analysis.is_sampled
256+
);
257+
}
258+
259+
println!(
260+
"✅ Example completed! Total spans analyzed: {}",
261+
results.len()
262+
);
263+
}

0 commit comments

Comments
 (0)