diff --git a/sentry-core/src/performance/headers.rs b/sentry-core/src/performance/headers.rs new file mode 100644 index 00000000..84666556 --- /dev/null +++ b/sentry-core/src/performance/headers.rs @@ -0,0 +1,88 @@ +//! Module containing utilities for interacting with Sentry tracing headers. + +use crate::protocol; + +/// A container for distributed tracing metadata that can be extracted from e.g. the `sentry-trace` +/// HTTP header. +#[derive(Debug, PartialEq, Clone, Copy, Default)] +pub struct SentryTrace { + pub(crate) trace_id: protocol::TraceId, + pub(crate) span_id: protocol::SpanId, + pub(crate) sampled: Option, +} + +impl SentryTrace { + /// Creates a new [`SentryTrace`] from the provided parameters + pub fn new( + trace_id: protocol::TraceId, + span_id: protocol::SpanId, + sampled: Option, + ) -> Self { + SentryTrace { + trace_id, + span_id, + sampled, + } + } +} + +fn parse_sentry_trace(header: &str) -> Option { + let header = header.trim(); + let mut parts = header.splitn(3, '-'); + + let trace_id = parts.next()?.parse().ok()?; + let parent_span_id = parts.next()?.parse().ok()?; + let parent_sampled = parts.next().and_then(|sampled| match sampled { + "1" => Some(true), + "0" => Some(false), + _ => None, + }); + + Some(SentryTrace::new(trace_id, parent_span_id, parent_sampled)) +} + +impl std::fmt::Display for SentryTrace { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}-{}", self.trace_id, self.span_id)?; + if let Some(sampled) = self.sampled { + write!(f, "-{}", if sampled { '1' } else { '0' })?; + } + Ok(()) + } +} + +/// Extracts distributed tracing metadata from headers (or, generally, key-value pairs), +/// considering the values for `sentry-trace`. +pub fn parse<'a, I: IntoIterator>(headers: I) -> Option { + let mut trace = None; + for (k, v) in headers.into_iter() { + if k.eq_ignore_ascii_case("sentry-trace") { + trace = parse_sentry_trace(v); + break; + } + } + trace +} + +#[cfg(test)] +mod tests { + use std::str::FromStr as _; + + use super::*; + + #[test] + fn parses_sentry_trace() { + let trace_id = protocol::TraceId::from_str("09e04486820349518ac7b5d2adbf6ba5").unwrap(); + let parent_trace_id = protocol::SpanId::from_str("9cf635fa5b870b3a").unwrap(); + + let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0"); + assert_eq!( + trace, + Some(SentryTrace::new(trace_id, parent_trace_id, Some(false))) + ); + + let trace = SentryTrace::new(Default::default(), Default::default(), None); + let parsed = parse_sentry_trace(&trace.to_string()); + assert_eq!(parsed, Some(trace)); + } +} diff --git a/sentry-core/src/performance.rs b/sentry-core/src/performance/mod.rs similarity index 94% rename from sentry-core/src/performance.rs rename to sentry-core/src/performance/mod.rs index ae6056e0..a790b32f 100644 --- a/sentry-core/src/performance.rs +++ b/sentry-core/src/performance/mod.rs @@ -15,6 +15,10 @@ use crate::{protocol, Hub}; #[cfg(feature = "client")] use crate::Client; +pub use self::headers::{parse as parse_headers, SentryTrace}; + +mod headers; + #[cfg(feature = "client")] const MAX_SPANS: usize = 1_000; @@ -1250,93 +1254,12 @@ impl Iterator for TraceHeadersIter { } } -/// A container for distributed tracing metadata that can be extracted from e.g. the `sentry-trace` -/// HTTP header. -#[derive(Debug, PartialEq, Clone, Copy, Default)] -pub struct SentryTrace { - pub(crate) trace_id: protocol::TraceId, - pub(crate) span_id: protocol::SpanId, - pub(crate) sampled: Option, -} - -impl SentryTrace { - /// Creates a new [`SentryTrace`] from the provided parameters - pub fn new( - trace_id: protocol::TraceId, - span_id: protocol::SpanId, - sampled: Option, - ) -> Self { - SentryTrace { - trace_id, - span_id, - sampled, - } - } -} - -fn parse_sentry_trace(header: &str) -> Option { - let header = header.trim(); - let mut parts = header.splitn(3, '-'); - - let trace_id = parts.next()?.parse().ok()?; - let parent_span_id = parts.next()?.parse().ok()?; - let parent_sampled = parts.next().and_then(|sampled| match sampled { - "1" => Some(true), - "0" => Some(false), - _ => None, - }); - - Some(SentryTrace::new(trace_id, parent_span_id, parent_sampled)) -} - -/// Extracts distributed tracing metadata from headers (or, generally, key-value pairs), -/// considering the values for `sentry-trace`. -pub fn parse_headers<'a, I: IntoIterator>( - headers: I, -) -> Option { - let mut trace = None; - for (k, v) in headers.into_iter() { - if k.eq_ignore_ascii_case("sentry-trace") { - trace = parse_sentry_trace(v); - break; - } - } - trace -} - -impl std::fmt::Display for SentryTrace { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}-{}", self.trace_id, self.span_id)?; - if let Some(sampled) = self.sampled { - write!(f, "-{}", if sampled { '1' } else { '0' })?; - } - Ok(()) - } -} - #[cfg(test)] mod tests { - use std::str::FromStr; use std::sync::Arc; use super::*; - #[test] - fn parses_sentry_trace() { - let trace_id = protocol::TraceId::from_str("09e04486820349518ac7b5d2adbf6ba5").unwrap(); - let parent_trace_id = protocol::SpanId::from_str("9cf635fa5b870b3a").unwrap(); - - let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0"); - assert_eq!( - trace, - Some(SentryTrace::new(trace_id, parent_trace_id, Some(false))) - ); - - let trace = SentryTrace::new(Default::default(), Default::default(), None); - let parsed = parse_sentry_trace(&trace.to_string()); - assert_eq!(parsed, Some(trace)); - } - #[test] fn disabled_forwards_trace_id() { let headers = [( @@ -1349,7 +1272,7 @@ mod tests { let span = trx.start_child("noop", "noop"); let header = span.iter_headers().next().unwrap().1; - let parsed = parse_sentry_trace(&header).unwrap(); + let parsed = parse_headers([("sentry-trace", header.as_str())]).unwrap(); assert_eq!( &parsed.trace_id.to_string(),