Skip to content

Commit 9006373

Browse files
committed
feat(tracer): add more convenience methods for enterings spans
1 parent 796ac1a commit 9006373

File tree

2 files changed

+332
-1
lines changed

2 files changed

+332
-1
lines changed

opentelemetry-sdk/src/trace/tracer.rs

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,4 +372,220 @@ mod tests {
372372

373373
assert!(!span.span_context().is_sampled());
374374
}
375+
376+
#[test]
377+
fn in_span_with_context_uses_provided_context() {
378+
use crate::trace::{InMemorySpanExporter, SimpleSpanProcessor};
379+
380+
let exporter = InMemorySpanExporter::default();
381+
let tracer_provider = crate::trace::SdkTracerProvider::builder()
382+
.with_sampler(Sampler::AlwaysOn)
383+
.with_span_processor(SimpleSpanProcessor::new(exporter.clone()))
384+
.build();
385+
let tracer = tracer_provider.tracer("test");
386+
387+
// Create a parent context explicitly
388+
let parent_span = tracer.start("parent");
389+
let parent_trace_id = parent_span.span_context().trace_id();
390+
let parent_span_id = parent_span.span_context().span_id();
391+
let parent_cx = Context::current_with_span(parent_span);
392+
393+
// Use in_span_with_context with explicit parent context
394+
let mut child_trace_id = None;
395+
let mut child_span_id = None;
396+
let mut executed = false;
397+
398+
let returned_value = tracer.in_span_with_context("child-span", &parent_cx, |cx| {
399+
let span = cx.span();
400+
child_trace_id = Some(span.span_context().trace_id());
401+
child_span_id = Some(span.span_context().span_id());
402+
executed = true;
403+
"test_result"
404+
});
405+
406+
// Verify child span inherited parent's trace_id
407+
assert_eq!(child_trace_id, Some(parent_trace_id));
408+
// Verify child has a different span_id than parent
409+
assert_ne!(child_span_id, Some(parent_span_id));
410+
// Verify the closure was executed
411+
assert!(executed);
412+
// Verify return value is passed through
413+
assert_eq!(returned_value, "test_result");
414+
415+
// End the parent span to export it
416+
drop(parent_cx);
417+
418+
// Verify parent-child relationship through exporter
419+
let spans = exporter.get_finished_spans().unwrap();
420+
assert_eq!(spans.len(), 2);
421+
let parent = spans.iter().find(|s| s.name == "parent").unwrap();
422+
let child = spans.iter().find(|s| s.name == "child-span").unwrap();
423+
assert_eq!(child.parent_span_id, parent.span_context.span_id());
424+
assert_eq!(child.span_context.trace_id(), parent.span_context.trace_id());
425+
}
426+
427+
#[test]
428+
fn in_span_with_builder_uses_current_context() {
429+
use crate::trace::{InMemorySpanExporter, SimpleSpanProcessor};
430+
431+
let exporter = InMemorySpanExporter::default();
432+
let tracer_provider = crate::trace::SdkTracerProvider::builder()
433+
.with_sampler(Sampler::AlwaysOn)
434+
.with_span_processor(SimpleSpanProcessor::new(exporter.clone()))
435+
.build();
436+
let tracer = tracer_provider.tracer("test");
437+
438+
// Create a parent span and attach it to the current context
439+
let parent_span = tracer.start("parent");
440+
let parent_trace_id = parent_span.span_context().trace_id();
441+
let parent_span_id = parent_span.span_context().span_id();
442+
let _attached = Context::current_with_span(parent_span).attach();
443+
444+
// Use in_span_with_builder with configured span
445+
let mut child_trace_id = None;
446+
447+
tracer.in_span_with_builder(
448+
tracer
449+
.span_builder("child")
450+
.with_kind(SpanKind::Client)
451+
.with_attributes(vec![KeyValue::new("test_key", "test_value")]),
452+
|cx| {
453+
let span = cx.span();
454+
child_trace_id = Some(span.span_context().trace_id());
455+
},
456+
);
457+
458+
// Verify child span inherited parent's trace_id
459+
assert_eq!(child_trace_id, Some(parent_trace_id));
460+
461+
// End the attached context to export the parent span
462+
drop(_attached);
463+
464+
// Verify parent-child relationship through exporter
465+
let spans = exporter.get_finished_spans().unwrap();
466+
assert_eq!(spans.len(), 2);
467+
let child = spans.iter().find(|s| s.name == "child").unwrap();
468+
assert_eq!(child.parent_span_id, parent_span_id);
469+
assert_eq!(child.span_context.trace_id(), parent_trace_id);
470+
}
471+
472+
#[test]
473+
fn in_span_with_builder_and_context_uses_provided_context() {
474+
use crate::trace::{InMemorySpanExporter, SimpleSpanProcessor};
475+
476+
let exporter = InMemorySpanExporter::default();
477+
let tracer_provider = crate::trace::SdkTracerProvider::builder()
478+
.with_sampler(Sampler::AlwaysOn)
479+
.with_span_processor(SimpleSpanProcessor::new(exporter.clone()))
480+
.build();
481+
let tracer = tracer_provider.tracer("test");
482+
483+
// Create a parent context explicitly
484+
let parent_span = tracer.start("parent");
485+
let parent_trace_id = parent_span.span_context().trace_id();
486+
let parent_span_id = parent_span.span_context().span_id();
487+
let parent_cx = Context::current_with_span(parent_span);
488+
489+
// Use in_span_with_builder_and_context with explicit parent context
490+
let mut child_trace_id = None;
491+
let mut result = 0;
492+
493+
let returned_value = tracer.in_span_with_builder_and_context(
494+
tracer
495+
.span_builder("child")
496+
.with_kind(SpanKind::Server)
497+
.with_attributes(vec![
498+
KeyValue::new("http.method", "GET"),
499+
KeyValue::new("http.url", "/api/test"),
500+
]),
501+
&parent_cx,
502+
|cx| {
503+
let span = cx.span();
504+
child_trace_id = Some(span.span_context().trace_id());
505+
result = 42;
506+
result
507+
},
508+
);
509+
510+
// Verify child span inherited parent's trace_id
511+
assert_eq!(child_trace_id, Some(parent_trace_id));
512+
// Verify return value is passed through
513+
assert_eq!(returned_value, 42);
514+
assert_eq!(result, 42);
515+
516+
// End the parent span to export it
517+
drop(parent_cx);
518+
519+
// Verify parent-child relationship through exporter
520+
let spans = exporter.get_finished_spans().unwrap();
521+
assert_eq!(spans.len(), 2);
522+
let child = spans.iter().find(|s| s.name == "child").unwrap();
523+
assert_eq!(child.parent_span_id, parent_span_id);
524+
assert_eq!(child.span_context.trace_id(), parent_trace_id);
525+
}
526+
527+
#[test]
528+
fn in_span_with_builder_and_context_ignores_active_context() {
529+
use crate::trace::{InMemorySpanExporter, SimpleSpanProcessor};
530+
531+
let exporter = InMemorySpanExporter::default();
532+
let tracer_provider = crate::trace::SdkTracerProvider::builder()
533+
.with_sampler(Sampler::AlwaysOn)
534+
.with_span_processor(SimpleSpanProcessor::new(exporter.clone()))
535+
.build();
536+
let tracer = tracer_provider.tracer("test");
537+
538+
// Create an active context with a specific trace context
539+
let active_span_context = SpanContext::new(
540+
TraceId::from(1u128),
541+
SpanId::from(1u64),
542+
TraceFlags::SAMPLED,
543+
true,
544+
Default::default(),
545+
);
546+
let active_trace_id = active_span_context.trace_id();
547+
let active_span_id = active_span_context.span_id();
548+
let _attached = Context::current_with_span(TestSpan(active_span_context)).attach();
549+
550+
// Create a different parent context with a different trace ID to explicitly provide
551+
let provided_span_context = SpanContext::new(
552+
TraceId::from(2u128),
553+
SpanId::from(2u64),
554+
TraceFlags::SAMPLED,
555+
true,
556+
Default::default(),
557+
);
558+
let provided_trace_id = provided_span_context.trace_id();
559+
let provided_span_id = provided_span_context.span_id();
560+
let provided_cx = Context::current_with_span(TestSpan(provided_span_context));
561+
562+
// Ensure the two parents have different trace IDs
563+
assert_ne!(active_trace_id, provided_trace_id);
564+
565+
// Use in_span_with_builder_and_context with explicit parent context
566+
let mut child_trace_id = None;
567+
568+
tracer.in_span_with_builder_and_context(
569+
tracer.span_builder("child").with_kind(SpanKind::Internal),
570+
&provided_cx,
571+
|cx| {
572+
let span = cx.span();
573+
child_trace_id = Some(span.span_context().trace_id());
574+
},
575+
);
576+
577+
// Verify child uses the provided context, NOT the active context
578+
assert_eq!(child_trace_id, Some(provided_trace_id));
579+
assert_ne!(child_trace_id, Some(active_trace_id));
580+
581+
// Verify parent-child relationship through exporter
582+
let spans = exporter.get_finished_spans().unwrap();
583+
assert_eq!(spans.len(), 1);
584+
let child = &spans[0];
585+
assert_eq!(child.name, "child");
586+
assert_eq!(child.parent_span_id, provided_span_id);
587+
assert_eq!(child.span_context.trace_id(), provided_trace_id);
588+
assert_ne!(child.parent_span_id, active_span_id);
589+
assert_ne!(child.span_context.trace_id(), active_trace_id);
590+
}
375591
}

opentelemetry/src/trace/tracer.rs

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,126 @@ pub trait Tracer {
207207
N: Into<Cow<'static, str>>,
208208
Self::Span: Send + Sync + 'static,
209209
{
210-
let span = self.start(name);
210+
self.in_span_with_builder(self.span_builder(name), f)
211+
}
212+
213+
/// Start a new span with a given parent context and execute the given closure with
214+
/// reference to the context in which the span is active.
215+
///
216+
/// This method starts a new span as a child of the provided parent context and sets
217+
/// it as the active span for the given function. It then executes the body. It ends
218+
/// the span before returning the execution result.
219+
///
220+
/// # Examples
221+
///
222+
/// ```
223+
/// use opentelemetry::{global, trace::{Span, Tracer, TraceContextExt}, Context, KeyValue};
224+
///
225+
/// fn my_function() {
226+
/// let tracer = global::tracer("my-component");
227+
///
228+
/// // Create a parent context
229+
/// let parent = tracer.start("parent-span");
230+
/// let parent_cx = Context::current_with_span(parent);
231+
///
232+
/// // start an active span with explicit parent context
233+
/// tracer.in_span_with_context("child-span", &parent_cx, |_cx| {
234+
/// // child span is active here
235+
/// })
236+
/// }
237+
/// ```
238+
fn in_span_with_context<T, F, N>(&self, name: N, parent_cx: &Context, f: F) -> T
239+
where
240+
F: FnOnce(Context) -> T,
241+
N: Into<Cow<'static, str>>,
242+
Self::Span: Send + Sync + 'static,
243+
{
244+
self.in_span_with_builder_and_context(self.span_builder(name), parent_cx, f)
245+
}
246+
247+
/// Start a new span from a [`SpanBuilder`] and execute the given closure with
248+
/// reference to the context in which the span is active.
249+
///
250+
/// This method builds and starts a new span from a [`SpanBuilder`] using the current
251+
/// context and sets it as the active span for the given function. It then executes
252+
/// the body. It ends the span before returning the execution result.
253+
///
254+
/// # Examples
255+
///
256+
/// ```
257+
/// use opentelemetry::{global, trace::{Span, SpanKind, Tracer}, KeyValue};
258+
///
259+
/// fn my_function() {
260+
/// let tracer = global::tracer("my-component");
261+
///
262+
/// // start an active span with span configuration
263+
/// tracer.in_span_with_builder(
264+
/// tracer.span_builder("span-name")
265+
/// .with_kind(SpanKind::Client)
266+
/// .with_attributes(vec![KeyValue::new("key", "value")]),
267+
/// |_cx| {
268+
/// // span is active here with configured attributes
269+
/// }
270+
/// )
271+
/// }
272+
/// ```
273+
fn in_span_with_builder<T, F>(&self, builder: SpanBuilder, f: F) -> T
274+
where
275+
F: FnOnce(Context) -> T,
276+
Self::Span: Send + Sync + 'static,
277+
{
278+
let span = self.build(builder);
211279
let cx = Context::current_with_span(span);
212280
let _guard = cx.clone().attach();
213281
f(cx)
214282
}
283+
284+
/// Start a new span from a [`SpanBuilder`] with a given parent context and execute the
285+
/// given closure with reference to the context in which the span is active.
286+
///
287+
/// This method builds and starts a new span from a [`SpanBuilder`] as a child of the
288+
/// provided parent context and sets it as the active span for the given function. It
289+
/// then executes the body. It ends the span before returning the execution result.
290+
///
291+
/// # Examples
292+
///
293+
/// ```
294+
/// use opentelemetry::{global, trace::{Span, SpanKind, Tracer, TraceContextExt}, Context, KeyValue};
295+
///
296+
/// fn my_function() {
297+
/// let tracer = global::tracer("my-component");
298+
///
299+
/// // Create a parent context
300+
/// let parent = tracer.start("parent-span");
301+
/// let parent_cx = Context::current_with_span(parent);
302+
///
303+
/// // start an active span with explicit parent context and span configuration
304+
/// tracer.in_span_with_builder_and_context(
305+
/// tracer.span_builder("child-span")
306+
/// .with_kind(SpanKind::Client)
307+
/// .with_attributes(vec![KeyValue::new("key", "value")]),
308+
/// &parent_cx,
309+
/// |_cx| {
310+
/// // child span is active here with configured attributes
311+
/// }
312+
/// )
313+
/// }
314+
/// ```
315+
fn in_span_with_builder_and_context<T, F>(
316+
&self,
317+
builder: SpanBuilder,
318+
parent_cx: &Context,
319+
f: F,
320+
) -> T
321+
where
322+
F: FnOnce(Context) -> T,
323+
Self::Span: Send + Sync + 'static,
324+
{
325+
let span = self.build_with_context(builder, parent_cx);
326+
let cx = parent_cx.with_span(span);
327+
let _guard = cx.clone().attach();
328+
f(cx)
329+
}
215330
}
216331

217332
/// `SpanBuilder` allows span attributes to be configured before the span

0 commit comments

Comments
 (0)