sentry_opentelemetry/
propagator.rs

1//! An OpenTelemetry [Propagator](https://opentelemetry.io/docs/specs/otel/context/api-propagators/) for Sentry.
2//!
3//! [`SentryPropagator`] serves two purposes:
4//! - extracts incoming Sentry tracing metadata from incoming traces, and stores it in
5//!   [`opentelemetry::baggage::Baggage`]. This information can then be used by
6//!   [`crate::processor::SentrySpanProcessor`] to achieve distributed tracing.
7//! - injects Sentry tracing metadata in outgoing traces. This information can be used by
8//!   downstream Sentry SDKs to achieve distributed tracing.
9//!
10//! # Configuration
11//!
12//! This should be used together with [`crate::processor::SentrySpanProcessor`]. An example of
13//! setting up both is provided in the [crate-level documentation](../).
14
15use std::sync::LazyLock;
16
17use opentelemetry::{
18    propagation::{text_map_propagator::FieldIter, Extractor, Injector, TextMapPropagator},
19    trace::TraceContextExt,
20    Context, SpanId, TraceId,
21};
22use sentry_core::parse_headers;
23use sentry_core::SentryTrace;
24
25use crate::converters::{convert_span_id, convert_trace_id};
26
27const SENTRY_TRACE_KEY: &str = "sentry-trace";
28
29// list of headers used in the inject operation
30static SENTRY_PROPAGATOR_FIELDS: LazyLock<[String; 1]> =
31    LazyLock::new(|| [SENTRY_TRACE_KEY.to_owned()]);
32
33/// An OpenTelemetry Propagator that injects and extracts Sentry's tracing headers to achieve
34/// distributed tracing.
35#[derive(Debug, Copy, Clone)]
36pub struct SentryPropagator {}
37
38impl SentryPropagator {
39    /// Creates a new `SentryPropagator`
40    pub fn new() -> Self {
41        Self {}
42    }
43}
44
45impl Default for SentryPropagator {
46    /// Creates a default `SentryPropagator`.
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl TextMapPropagator for SentryPropagator {
53    fn inject_context(&self, ctx: &Context, injector: &mut dyn Injector) {
54        let trace_id = ctx.span().span_context().trace_id();
55        let span_id = ctx.span().span_context().span_id();
56        let sampled = ctx.span().span_context().is_sampled();
57        if trace_id == TraceId::INVALID || span_id == SpanId::INVALID {
58            return;
59        }
60        let sentry_trace = SentryTrace::new(
61            convert_trace_id(&trace_id),
62            convert_span_id(&span_id),
63            Some(sampled),
64        );
65        injector.set(SENTRY_TRACE_KEY, sentry_trace.to_string());
66    }
67
68    fn extract_with_context(&self, ctx: &Context, extractor: &dyn Extractor) -> Context {
69        let keys = extractor.keys();
70        let pairs = keys
71            .iter()
72            .filter_map(|&key| extractor.get(key).map(|value| (key, value)));
73        if let Some(sentry_trace) = parse_headers(pairs) {
74            return ctx.with_value(sentry_trace);
75        }
76        ctx.clone()
77    }
78
79    fn fields(&self) -> FieldIter<'_> {
80        FieldIter::new(&*SENTRY_PROPAGATOR_FIELDS)
81    }
82}