sentry_opentelemetry/
processor.rs1use std::collections::HashMap;
12use std::sync::{Arc, LazyLock, Mutex};
13use std::time::SystemTime;
14
15use opentelemetry::global::ObjectSafeSpan;
16use opentelemetry::trace::{get_active_span, SpanId};
17use opentelemetry::Context;
18use opentelemetry_sdk::error::OTelSdkResult;
19use opentelemetry_sdk::trace::{Span, SpanData, SpanProcessor};
20
21use opentelemetry_sdk::Resource;
22use sentry_core::SentryTrace;
23use sentry_core::{TransactionContext, TransactionOrSpan};
24
25use crate::converters::{
26 convert_span_id, convert_span_kind, convert_span_status, convert_trace_id, convert_value,
27};
28
29type SpanMap = Arc<Mutex<HashMap<sentry_core::protocol::SpanId, TransactionOrSpan>>>;
33
34static SPAN_MAP: LazyLock<SpanMap> = LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));
35
36#[derive(Debug, Clone)]
39pub struct SentrySpanProcessor {}
40
41impl SentrySpanProcessor {
42 pub fn new() -> Self {
44 sentry_core::configure_scope(|scope| {
45 scope.add_event_processor(|mut event| {
48 get_active_span(|otel_span| {
49 let span_map = SPAN_MAP.lock().unwrap();
50
51 let Some(sentry_span) =
52 span_map.get(&convert_span_id(&otel_span.span_context().span_id()))
53 else {
54 return;
55 };
56
57 let (span_id, trace_id) = match sentry_span {
58 TransactionOrSpan::Transaction(transaction) => (
59 transaction.get_trace_context().span_id,
60 transaction.get_trace_context().trace_id,
61 ),
62 TransactionOrSpan::Span(span) => {
63 (span.get_span_id(), span.get_trace_context().trace_id)
64 }
65 };
66
67 if let Some(sentry_core::protocol::Context::Trace(trace_context)) =
68 event.contexts.get_mut("trace")
69 {
70 trace_context.trace_id = trace_id;
71 trace_context.span_id = span_id;
72 } else {
73 event.contexts.insert(
74 "trace".into(),
75 sentry_core::protocol::TraceContext {
76 span_id,
77 trace_id,
78 ..Default::default()
79 }
80 .into(),
81 );
82 }
83 });
84 Some(event)
85 });
86 });
87 Self {}
88 }
89}
90
91impl Default for SentrySpanProcessor {
92 fn default() -> Self {
94 Self::new()
95 }
96}
97
98impl SpanProcessor for SentrySpanProcessor {
99 fn on_start(&self, span: &mut Span, ctx: &Context) {
100 let span_id = span.span_context().span_id();
101 let trace_id = span.span_context().trace_id();
102
103 let mut span_map = SPAN_MAP.lock().unwrap();
104
105 let mut span_description = String::new();
106 let mut span_op = String::new();
107 let mut span_start_timestamp = SystemTime::now();
108 let mut parent_sentry_span = None;
109 if let Some(data) = span.exported_data() {
110 span_description = data.name.to_string();
111 span_op = span_description.clone(); span_start_timestamp = data.start_time;
113 if data.parent_span_id != SpanId::INVALID {
114 parent_sentry_span = span_map.get(&convert_span_id(&data.parent_span_id));
115 };
116 }
117 let span_description = span_description.as_str();
118 let span_op = span_op.as_str();
119
120 let sentry_span = {
121 if let Some(parent_sentry_span) = parent_sentry_span {
122 TransactionOrSpan::Span(parent_sentry_span.start_child_with_details(
124 span_op,
125 span_description,
126 convert_span_id(&span_id),
127 span_start_timestamp,
128 ))
129 } else {
130 let sentry_ctx = {
131 if let Some(sentry_trace) = ctx.get::<SentryTrace>() {
132 TransactionContext::continue_from_sentry_trace(
134 span_description,
135 span_op,
136 sentry_trace,
137 Some(convert_span_id(&span_id)),
138 )
139 } else {
140 TransactionContext::new_with_details(
142 span_description,
143 span_op,
144 convert_trace_id(&trace_id),
145 Some(convert_span_id(&span_id)),
146 None,
147 )
148 }
149 };
150 let tx =
151 sentry_core::start_transaction_with_timestamp(sentry_ctx, span_start_timestamp);
152 TransactionOrSpan::Transaction(tx)
153 }
154 };
155 span_map.insert(convert_span_id(&span_id), sentry_span);
156 }
157
158 fn on_end(&self, data: SpanData) {
159 let span_id = data.span_context.span_id();
160
161 let mut span_map = SPAN_MAP.lock().unwrap();
162
163 let Some(sentry_span) = span_map.remove(&convert_span_id(&span_id)) else {
164 return;
165 };
166
167 sentry_span.set_data("otel.kind", convert_span_kind(data.span_kind));
170 for attribute in data.attributes {
171 sentry_span.set_data(attribute.key.as_str(), convert_value(attribute.value));
172 }
173
174 if let TransactionOrSpan::Transaction(transaction) = &sentry_span {
178 transaction.set_origin("auto.otel");
179 }
180 sentry_span.set_status(convert_span_status(&data.status));
181 sentry_span.finish_with_timestamp(data.end_time);
182 }
183
184 fn force_flush(&self) -> OTelSdkResult {
185 Ok(())
186 }
187
188 fn shutdown(&self) -> OTelSdkResult {
189 Ok(())
190 }
191
192 fn set_resource(&mut self, resource: &Resource) {
193 sentry_core::configure_scope(|scope| {
194 let otel_context = sentry_core::protocol::OtelContext {
195 resource: resource
196 .iter()
197 .map(|(key, value)| (key.as_str().into(), convert_value(value.clone())))
198 .collect(),
199 ..Default::default()
200 };
201 scope.set_context("otel", sentry_core::protocol::Context::from(otel_context));
202 });
203 }
204}