Skip to content
Merged
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
# Changelog

### Unreleased
## Unreleased

### Breaking changes

- refactor: remove `debug-logs` feature (#820) by @lcian
- The deprecated `debug-logs` feature of the `sentry` crate has been removed.
- The deprecated `debug-logs` feature of the `sentry` crate, used for the SDK's own internal logging, has been removed.

### Behavioral changes

- feat(core): implement Tracing without Performance (#811) by @lcian
- The SDK now implements Tracing without Performance, which makes it so that each `Scope` is associated with an object holding some tracing information.
- This information is used as a fallback when capturing an event with tracing disabled or otherwise no ongoing span, to still allow related events to be linked by a trace.
- A new API `Scope::iter_trace_propagation_headers` has been provided that will use the fallback tracing information if there is no current `Span` on the `Scope`.

## 0.38.1

Expand Down
61 changes: 44 additions & 17 deletions sentry-core/src/performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,12 @@ impl TransactionContext {
sentry_trace: &SentryTrace,
span_id: Option<SpanId>,
) -> Self {
let (trace_id, parent_span_id, sampled) =
(sentry_trace.0, Some(sentry_trace.1), sentry_trace.2);
Self {
name: name.into(),
op: op.into(),
trace_id,
parent_span_id,
sampled,
trace_id: sentry_trace.trace_id,
parent_span_id: Some(sentry_trace.span_id),
sampled: sentry_trace.sampled,
span_id: span_id.unwrap_or_default(),
custom: None,
}
Expand Down Expand Up @@ -476,6 +474,8 @@ impl TransactionOrSpan {
}

/// Returns the headers needed for distributed tracing.
/// Use [`crate::Scope::iter_trace_propagation_headers`] to obtain the active
/// trace's distributed tracing headers.
pub fn iter_headers(&self) -> TraceHeadersIter {
match self {
TransactionOrSpan::Transaction(transaction) => transaction.iter_headers(),
Expand Down Expand Up @@ -774,9 +774,11 @@ impl Transaction {
}

/// Returns the headers needed for distributed tracing.
/// Use [`crate::Scope::iter_trace_propagation_headers`] to obtain the active
/// trace's distributed tracing headers.
pub fn iter_headers(&self) -> TraceHeadersIter {
let inner = self.inner.lock().unwrap();
let trace = SentryTrace(
let trace = SentryTrace::new(
inner.context.trace_id,
inner.context.span_id,
Some(inner.sampled),
Expand Down Expand Up @@ -1026,9 +1028,11 @@ impl Span {
}

/// Returns the headers needed for distributed tracing.
/// Use [`crate::Scope::iter_trace_propagation_headers`] to obtain the active
/// trace's distributed tracing headers.
pub fn iter_headers(&self) -> TraceHeadersIter {
let span = self.span.lock().unwrap();
let trace = SentryTrace(span.trace_id, span.span_id, Some(self.sampled));
let trace = SentryTrace::new(span.trace_id, span.span_id, Some(self.sampled));
TraceHeadersIter {
sentry_trace: Some(trace.to_string()),
}
Expand Down Expand Up @@ -1125,6 +1129,9 @@ impl Span {
}
}

/// Represents a key-value pair such as an HTTP header.
pub type TraceHeader = (&'static str, String);

/// An Iterator over HTTP header names and values needed for distributed tracing.
///
/// This currently only yields the `sentry-trace` header, but other headers
Expand All @@ -1133,6 +1140,15 @@ pub struct TraceHeadersIter {
sentry_trace: Option<String>,
}

impl TraceHeadersIter {
#[cfg(feature = "client")]
pub(crate) fn new(sentry_trace: String) -> Self {
Self {
sentry_trace: Some(sentry_trace),
}
}
}

impl Iterator for TraceHeadersIter {
type Item = (&'static str, String);

Expand All @@ -1143,8 +1159,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)]
pub struct SentryTrace(protocol::TraceId, protocol::SpanId, Option<bool>);
#[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<bool>,
}

impl SentryTrace {
/// Creates a new [`SentryTrace`] from the provided parameters
Expand All @@ -1153,7 +1173,11 @@ impl SentryTrace {
span_id: protocol::SpanId,
sampled: Option<bool>,
) -> Self {
SentryTrace(trace_id, span_id, sampled)
SentryTrace {
trace_id,
span_id,
sampled,
}
}
}

Expand All @@ -1169,7 +1193,7 @@ fn parse_sentry_trace(header: &str) -> Option<SentryTrace> {
_ => None,
});

Some(SentryTrace(trace_id, parent_span_id, parent_sampled))
Some(SentryTrace::new(trace_id, parent_span_id, parent_sampled))
}

/// Extracts distributed tracing metadata from headers (or, generally, key-value pairs),
Expand All @@ -1189,8 +1213,8 @@ pub fn parse_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(

impl std::fmt::Display for SentryTrace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{}", self.0, self.1)?;
if let Some(sampled) = self.2 {
write!(f, "{}-{}", self.trace_id, self.span_id)?;
if let Some(sampled) = self.sampled {
write!(f, "-{}", if sampled { '1' } else { '0' })?;
}
Ok(())
Expand All @@ -1211,10 +1235,10 @@ mod tests {
let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0");
assert_eq!(
trace,
Some(SentryTrace(trace_id, parent_trace_id, Some(false)))
Some(SentryTrace::new(trace_id, parent_trace_id, Some(false)))
);

let trace = SentryTrace(Default::default(), Default::default(), None);
let trace = SentryTrace::new(Default::default(), Default::default(), None);
let parsed = parse_sentry_trace(&trace.to_string());
assert_eq!(parsed, Some(trace));
}
Expand All @@ -1233,8 +1257,11 @@ mod tests {
let header = span.iter_headers().next().unwrap().1;
let parsed = parse_sentry_trace(&header).unwrap();

assert_eq!(&parsed.0.to_string(), "09e04486820349518ac7b5d2adbf6ba5");
assert_eq!(parsed.2, Some(true));
assert_eq!(
&parsed.trace_id.to_string(),
"09e04486820349518ac7b5d2adbf6ba5"
);
assert_eq!(parsed.sampled, Some(true));
}

#[test]
Expand Down
35 changes: 34 additions & 1 deletion sentry-core/src/scope/real.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ use std::fmt;
use std::sync::Mutex;
use std::sync::{Arc, PoisonError, RwLock};

use sentry_types::protocol::v7::TraceContext;

use crate::performance::TransactionOrSpan;
use crate::protocol::{Attachment, Breadcrumb, Context, Event, Level, Transaction, User, Value};
#[cfg(feature = "release-health")]
use crate::session::Session;
use crate::Client;
use crate::{Client, SentryTrace, TraceHeader, TraceHeadersIter};

#[derive(Debug)]
pub struct Stack {
Expand Down Expand Up @@ -52,6 +54,7 @@ pub struct Scope {
pub(crate) session: Arc<Mutex<Option<Session>>>,
pub(crate) span: Arc<Option<TransactionOrSpan>>,
pub(crate) attachments: Arc<Vec<Attachment>>,
pub(crate) propagation_context: SentryTrace,
}

impl fmt::Debug for Scope {
Expand All @@ -74,6 +77,7 @@ impl fmt::Debug for Scope {
debug_struct
.field("span", &self.span)
.field("attachments", &self.attachments.len())
.field("propagation_context", &self.propagation_context)
.finish()
}
}
Expand Down Expand Up @@ -289,6 +293,8 @@ impl Scope {

if let Some(span) = self.span.as_ref() {
span.apply_to_event(&mut event);
} else {
self.apply_propagation_context(&mut event);
}

if event.transaction.is_none() {
Expand Down Expand Up @@ -357,4 +363,31 @@ impl Scope {
session.update_from_event(event);
}
}

pub(crate) fn apply_propagation_context(&self, event: &mut Event<'_>) {
if event.contexts.contains_key("trace") {
return;
}

let context = TraceContext {
trace_id: self.propagation_context.trace_id,
span_id: self.propagation_context.span_id,
..Default::default()
};
event.contexts.insert("trace".into(), context.into());
}

/// Returns the headers needed for distributed tracing.
pub fn iter_trace_propagation_headers(&self) -> impl Iterator<Item = TraceHeader> {
if let Some(span) = self.get_span() {
span.iter_headers()
} else {
let data = SentryTrace::new(
self.propagation_context.trace_id,
self.propagation_context.span_id,
None,
);
TraceHeadersIter::new(data.to_string())
}
}
}
16 changes: 14 additions & 2 deletions sentry-types/src/protocol/v7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1352,7 +1352,7 @@ pub struct OtelContext {
}

/// Holds the identifier for a Span
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash)]
#[serde(try_from = "String", into = "String")]
pub struct SpanId([u8; 8]);

Expand All @@ -1368,6 +1368,12 @@ impl fmt::Display for SpanId {
}
}

impl fmt::Debug for SpanId {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "SpanId({})", self)
}
}

impl From<SpanId> for String {
fn from(span_id: SpanId) -> Self {
span_id.to_string()
Expand Down Expand Up @@ -1399,7 +1405,7 @@ impl From<[u8; 8]> for SpanId {
}

/// Holds the identifier for a Trace
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash)]
#[serde(try_from = "String", into = "String")]
pub struct TraceId([u8; 16]);

Expand All @@ -1415,6 +1421,12 @@ impl fmt::Display for TraceId {
}
}

impl fmt::Debug for TraceId {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "TraceId({})", self)
}
}

impl From<TraceId> for String {
fn from(trace_id: TraceId) -> Self {
trace_id.to_string()
Expand Down
21 changes: 20 additions & 1 deletion sentry/tests/test_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

use sentry::protocol::{Attachment, EnvelopeItem};
use sentry::protocol::{Attachment, Context, EnvelopeItem};
use sentry::types::Uuid;

#[test]
Expand All @@ -28,6 +28,25 @@ fn test_basic_capture_message() {
assert_eq!(Some(event.event_id), last_event_id);
}

#[test]
fn test_event_trace_context_from_propagation_context() {
let mut last_event_id = None::<Uuid>;
let mut span = None;
let events = sentry::test::with_captured_events(|| {
sentry::configure_scope(|scope| {
span = scope.get_span();
});
sentry::capture_message("Hello World!", sentry::Level::Warning);
last_event_id = sentry::last_event_id();
});
assert_eq!(events.len(), 1);
let event = events.into_iter().next().unwrap();

let trace_context = event.contexts.get("trace");
assert!(span.is_none());
assert!(matches!(trace_context, Some(Context::Trace(_))));
}

#[test]
fn test_breadcrumbs() {
let events = sentry::test::with_captured_events(|| {
Expand Down