1use std::borrow::Cow;
2use std::cell::RefCell;
3use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use sentry_core::protocol::Value;
7use sentry_core::{Hub, HubSwitchGuard, TransactionOrSpan};
8use tracing_core::field::Visit;
9use tracing_core::{span, Field, Metadata, Subscriber};
10use tracing_subscriber::layer::{Context, Layer};
11use tracing_subscriber::registry::LookupSpan;
12
13use super::span_guard_stack::SpanGuardStack;
14use crate::converters::FieldVisitor;
15use crate::{SENTRY_NAME_FIELD, SENTRY_OP_FIELD, SENTRY_TRACE_FIELD, TAGS_PREFIX};
16
17#[derive(Default)]
27#[non_exhaustive]
28#[must_use]
29pub struct SpanLayer {}
30
31impl SpanLayer {
32 pub fn new() -> Self {
55 Self::default()
56 }
57}
58
59#[inline(always)]
60fn record_fields<'a, K: AsRef<str> + Into<Cow<'a, str>>>(
61 span: &TransactionOrSpan,
62 data: BTreeMap<K, Value>,
63) {
64 match span {
65 TransactionOrSpan::Span(span) => {
66 let mut span = span.data();
67 for (key, value) in data {
68 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
69 match value {
70 Value::Bool(value) => {
71 span.set_tag(stripped_key.to_owned(), value.to_string())
72 }
73 Value::Number(value) => {
74 span.set_tag(stripped_key.to_owned(), value.to_string())
75 }
76 Value::String(value) => span.set_tag(stripped_key.to_owned(), value),
77 _ => span.set_data(key.into().into_owned(), value),
78 }
79 } else {
80 span.set_data(key.into().into_owned(), value);
81 }
82 }
83 }
84 TransactionOrSpan::Transaction(transaction) => {
85 let mut transaction = transaction.data();
86 for (key, value) in data {
87 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
88 match value {
89 Value::Bool(value) => {
90 transaction.set_tag(stripped_key.into(), value.to_string())
91 }
92 Value::Number(value) => {
93 transaction.set_tag(stripped_key.into(), value.to_string())
94 }
95 Value::String(value) => transaction.set_tag(stripped_key.into(), value),
96 _ => transaction.set_data(key.into(), value),
97 }
98 } else {
99 transaction.set_data(key.into(), value);
100 }
101 }
102 }
103 }
104}
105
106pub(crate) struct SentrySpanData {
109 pub(crate) sentry_span: TransactionOrSpan,
110 hub: Arc<Hub>,
111}
112
113impl<S> Layer<S> for SpanLayer
114where
115 S: Subscriber + for<'a> LookupSpan<'a>,
116{
117 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
120 let span = match ctx.span(id) {
121 Some(span) => span,
122 None => return,
123 };
124
125 let (data, sentry_name, sentry_op, sentry_trace) = extract_span_data(attrs);
126 let sentry_name = sentry_name.as_deref().unwrap_or_else(|| span.name());
127 let sentry_op =
128 sentry_op.unwrap_or_else(|| format!("{}::{}", span.metadata().target(), span.name()));
129
130 let hub = Hub::current();
131 let parent_sentry_span = hub.configure_scope(|scope| scope.get_span());
132
133 let mut sentry_span: TransactionOrSpan = match &parent_sentry_span {
134 Some(parent) => parent.start_child(&sentry_op, sentry_name).into(),
135 None => {
136 let ctx = if let Some(trace_header) = sentry_trace {
137 sentry_core::TransactionContext::continue_from_headers(
138 sentry_name,
139 &sentry_op,
140 [("sentry-trace", trace_header.as_str())],
141 )
142 } else {
143 sentry_core::TransactionContext::new(sentry_name, &sentry_op)
144 };
145
146 let tx = sentry_core::start_transaction(ctx);
147 tx.set_origin("auto.tracing");
148 tx.into()
149 }
150 };
151 record_fields(&sentry_span, data);
152 set_default_attributes(&mut sentry_span, span.metadata());
153
154 let mut extensions = span.extensions_mut();
155 extensions.insert(SentrySpanData { sentry_span, hub });
156 }
157
158 fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
169 let span = match ctx.span(id) {
170 Some(span) => span,
171 None => return,
172 };
173
174 let extensions = span.extensions();
175 if let Some(data) = extensions.get::<SentrySpanData>() {
176 let hub = Arc::new(Hub::new_from_top(&data.hub));
177 hub.configure_scope(|scope| {
178 scope.set_span(Some(data.sentry_span.clone()));
179 });
180
181 let guard = HubSwitchGuard::new(hub);
182 SPAN_GUARDS.with(|guards| {
183 guards.borrow_mut().push(id.clone(), guard);
184 });
185 }
186 }
187
188 fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
190 let popped = SPAN_GUARDS.with(|guards| guards.borrow_mut().pop(id.clone()));
191
192 sentry_core::debug_assert_or_log!(
193 popped.is_some()
194 || ctx
195 .span(id)
196 .is_none_or(|span| span.extensions().get::<SentrySpanData>().is_none()),
197 "[SentrySpanLayer] missing HubSwitchGuard on exit for span {id:?}. \
198 This span has been exited more times on this thread than it has been entered, \
199 likely due to dropping an `Entered` guard in a different thread than where it was \
200 entered. This mismatch will likely cause the sentry-tracing layer to leak memory."
201 );
202 }
203
204 fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
206 let span = match ctx.span(&id) {
207 Some(span) => span,
208 None => return,
209 };
210
211 let mut extensions = span.extensions_mut();
212 let SentrySpanData { sentry_span, .. } = match extensions.remove::<SentrySpanData>() {
213 Some(data) => data,
214 None => return,
215 };
216 sentry_span.finish();
217 }
218
219 fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
221 let span = match ctx.span(span) {
222 Some(s) => s,
223 _ => return,
224 };
225
226 let mut extensions = span.extensions_mut();
227 let span = match extensions.get_mut::<SentrySpanData>() {
228 Some(t) => &t.sentry_span,
229 _ => return,
230 };
231
232 let mut data = FieldVisitor::default();
233 values.record(&mut data);
234
235 let sentry_name = data
236 .json_values
237 .remove(SENTRY_NAME_FIELD)
238 .and_then(|v| match v {
239 Value::String(s) => Some(s),
240 _ => None,
241 });
242
243 let sentry_op = data
244 .json_values
245 .remove(SENTRY_OP_FIELD)
246 .and_then(|v| match v {
247 Value::String(s) => Some(s),
248 _ => None,
249 });
250
251 data.json_values.remove(SENTRY_TRACE_FIELD);
253
254 if let Some(name) = sentry_name {
255 span.set_name(&name);
256 }
257 if let Some(op) = sentry_op {
258 span.set_op(&op);
259 }
260
261 record_fields(span, data.json_values);
262 }
263}
264
265fn set_default_attributes(span: &mut TransactionOrSpan, metadata: &Metadata<'_>) {
266 span.set_data("sentry.tracing.target", metadata.target().into());
267
268 if let Some(module) = metadata.module_path() {
269 span.set_data("code.module.name", module.into());
270 }
271 if let Some(file) = metadata.file() {
272 span.set_data("code.file.path", file.into());
273 }
274 if let Some(line) = metadata.line() {
275 span.set_data("code.line.number", line.into());
276 }
277}
278
279fn extract_span_data(
281 attrs: &span::Attributes,
282) -> (
283 BTreeMap<&'static str, Value>,
284 Option<String>,
285 Option<String>,
286 Option<String>,
287) {
288 let mut json_values = VISITOR_BUFFER.with_borrow_mut(|debug_buffer| {
289 let mut visitor = SpanFieldVisitor {
290 debug_buffer,
291 json_values: Default::default(),
292 };
293 attrs.record(&mut visitor);
294 visitor.json_values
295 });
296
297 let name = json_values.remove(SENTRY_NAME_FIELD).and_then(|v| match v {
298 Value::String(s) => Some(s),
299 _ => None,
300 });
301 let op = json_values.remove(SENTRY_OP_FIELD).and_then(|v| match v {
302 Value::String(s) => Some(s),
303 _ => None,
304 });
305 let sentry_trace = json_values
306 .remove(SENTRY_TRACE_FIELD)
307 .and_then(|v| match v {
308 Value::String(s) => Some(s),
309 _ => None,
310 });
311
312 (json_values, name, op, sentry_trace)
313}
314
315thread_local! {
316 static VISITOR_BUFFER: RefCell<String> = const { RefCell::new(String::new()) };
317 static SPAN_GUARDS: RefCell<SpanGuardStack> = RefCell::new(SpanGuardStack::new());
322}
323
324struct SpanFieldVisitor<'s> {
326 debug_buffer: &'s mut String,
327 json_values: BTreeMap<&'static str, Value>,
328}
329
330impl SpanFieldVisitor<'_> {
331 fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
332 self.json_values.insert(field.name(), value.into());
333 }
334}
335
336impl Visit for SpanFieldVisitor<'_> {
337 fn record_i64(&mut self, field: &Field, value: i64) {
338 self.record(field, value);
339 }
340 fn record_u64(&mut self, field: &Field, value: u64) {
341 self.record(field, value);
342 }
343 fn record_bool(&mut self, field: &Field, value: bool) {
344 self.record(field, value);
345 }
346 fn record_f64(&mut self, field: &Field, value: f64) {
347 self.record(field, value);
348 }
349 fn record_str(&mut self, field: &Field, value: &str) {
350 self.record(field, value);
351 }
352 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
353 use std::fmt::Write;
354 self.debug_buffer.reserve(128);
355 write!(self.debug_buffer, "{value:?}").unwrap();
356 self.json_values
357 .insert(field.name(), self.debug_buffer.as_str().into());
358 self.debug_buffer.clear();
359 }
360}