Skip to content

Commit cb7be0e

Browse files
feat(core): implement Tracing without Performance (#811)
Co-authored-by: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com>
1 parent ae9779f commit cb7be0e

File tree

5 files changed

+121
-23
lines changed

5 files changed

+121
-23
lines changed

CHANGELOG.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
# Changelog
22

3-
### Unreleased
3+
## Unreleased
44

55
### Breaking changes
66

77
- refactor: remove `debug-logs` feature (#820) by @lcian
8-
- The deprecated `debug-logs` feature of the `sentry` crate has been removed.
8+
- The deprecated `debug-logs` feature of the `sentry` crate, used for the SDK's own internal logging, has been removed.
9+
10+
### Behavioral changes
11+
12+
- feat(core): implement Tracing without Performance (#811) by @lcian
13+
- The SDK now implements Tracing without Performance, which makes it so that each `Scope` is associated with an object holding some tracing information.
14+
- 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.
15+
- 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`.
916

1017
## 0.38.1
1118

sentry-core/src/performance.rs

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,12 @@ impl TransactionContext {
199199
sentry_trace: &SentryTrace,
200200
span_id: Option<SpanId>,
201201
) -> Self {
202-
let (trace_id, parent_span_id, sampled) =
203-
(sentry_trace.0, Some(sentry_trace.1), sentry_trace.2);
204202
Self {
205203
name: name.into(),
206204
op: op.into(),
207-
trace_id,
208-
parent_span_id,
209-
sampled,
205+
trace_id: sentry_trace.trace_id,
206+
parent_span_id: Some(sentry_trace.span_id),
207+
sampled: sentry_trace.sampled,
210208
span_id: span_id.unwrap_or_default(),
211209
custom: None,
212210
}
@@ -476,6 +474,8 @@ impl TransactionOrSpan {
476474
}
477475

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

776776
/// Returns the headers needed for distributed tracing.
777+
/// Use [`crate::Scope::iter_trace_propagation_headers`] to obtain the active
778+
/// trace's distributed tracing headers.
777779
pub fn iter_headers(&self) -> TraceHeadersIter {
778780
let inner = self.inner.lock().unwrap();
779-
let trace = SentryTrace(
781+
let trace = SentryTrace::new(
780782
inner.context.trace_id,
781783
inner.context.span_id,
782784
Some(inner.sampled),
@@ -1026,9 +1028,11 @@ impl Span {
10261028
}
10271029

10281030
/// Returns the headers needed for distributed tracing.
1031+
/// Use [`crate::Scope::iter_trace_propagation_headers`] to obtain the active
1032+
/// trace's distributed tracing headers.
10291033
pub fn iter_headers(&self) -> TraceHeadersIter {
10301034
let span = self.span.lock().unwrap();
1031-
let trace = SentryTrace(span.trace_id, span.span_id, Some(self.sampled));
1035+
let trace = SentryTrace::new(span.trace_id, span.span_id, Some(self.sampled));
10321036
TraceHeadersIter {
10331037
sentry_trace: Some(trace.to_string()),
10341038
}
@@ -1125,6 +1129,9 @@ impl Span {
11251129
}
11261130
}
11271131

1132+
/// Represents a key-value pair such as an HTTP header.
1133+
pub type TraceHeader = (&'static str, String);
1134+
11281135
/// An Iterator over HTTP header names and values needed for distributed tracing.
11291136
///
11301137
/// This currently only yields the `sentry-trace` header, but other headers
@@ -1133,6 +1140,15 @@ pub struct TraceHeadersIter {
11331140
sentry_trace: Option<String>,
11341141
}
11351142

1143+
impl TraceHeadersIter {
1144+
#[cfg(feature = "client")]
1145+
pub(crate) fn new(sentry_trace: String) -> Self {
1146+
Self {
1147+
sentry_trace: Some(sentry_trace),
1148+
}
1149+
}
1150+
}
1151+
11361152
impl Iterator for TraceHeadersIter {
11371153
type Item = (&'static str, String);
11381154

@@ -1143,8 +1159,12 @@ impl Iterator for TraceHeadersIter {
11431159

11441160
/// A container for distributed tracing metadata that can be extracted from e.g. the `sentry-trace`
11451161
/// HTTP header.
1146-
#[derive(Debug, PartialEq)]
1147-
pub struct SentryTrace(protocol::TraceId, protocol::SpanId, Option<bool>);
1162+
#[derive(Debug, PartialEq, Clone, Copy, Default)]
1163+
pub struct SentryTrace {
1164+
pub(crate) trace_id: protocol::TraceId,
1165+
pub(crate) span_id: protocol::SpanId,
1166+
pub(crate) sampled: Option<bool>,
1167+
}
11481168

11491169
impl SentryTrace {
11501170
/// Creates a new [`SentryTrace`] from the provided parameters
@@ -1153,7 +1173,11 @@ impl SentryTrace {
11531173
span_id: protocol::SpanId,
11541174
sampled: Option<bool>,
11551175
) -> Self {
1156-
SentryTrace(trace_id, span_id, sampled)
1176+
SentryTrace {
1177+
trace_id,
1178+
span_id,
1179+
sampled,
1180+
}
11571181
}
11581182
}
11591183

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

1172-
Some(SentryTrace(trace_id, parent_span_id, parent_sampled))
1196+
Some(SentryTrace::new(trace_id, parent_span_id, parent_sampled))
11731197
}
11741198

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

11901214
impl std::fmt::Display for SentryTrace {
11911215
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1192-
write!(f, "{}-{}", self.0, self.1)?;
1193-
if let Some(sampled) = self.2 {
1216+
write!(f, "{}-{}", self.trace_id, self.span_id)?;
1217+
if let Some(sampled) = self.sampled {
11941218
write!(f, "-{}", if sampled { '1' } else { '0' })?;
11951219
}
11961220
Ok(())
@@ -1211,10 +1235,10 @@ mod tests {
12111235
let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0");
12121236
assert_eq!(
12131237
trace,
1214-
Some(SentryTrace(trace_id, parent_trace_id, Some(false)))
1238+
Some(SentryTrace::new(trace_id, parent_trace_id, Some(false)))
12151239
);
12161240

1217-
let trace = SentryTrace(Default::default(), Default::default(), None);
1241+
let trace = SentryTrace::new(Default::default(), Default::default(), None);
12181242
let parsed = parse_sentry_trace(&trace.to_string());
12191243
assert_eq!(parsed, Some(trace));
12201244
}
@@ -1233,8 +1257,11 @@ mod tests {
12331257
let header = span.iter_headers().next().unwrap().1;
12341258
let parsed = parse_sentry_trace(&header).unwrap();
12351259

1236-
assert_eq!(&parsed.0.to_string(), "09e04486820349518ac7b5d2adbf6ba5");
1237-
assert_eq!(parsed.2, Some(true));
1260+
assert_eq!(
1261+
&parsed.trace_id.to_string(),
1262+
"09e04486820349518ac7b5d2adbf6ba5"
1263+
);
1264+
assert_eq!(parsed.sampled, Some(true));
12381265
}
12391266

12401267
#[test]

sentry-core/src/scope/real.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use std::fmt;
55
use std::sync::Mutex;
66
use std::sync::{Arc, PoisonError, RwLock};
77

8+
use sentry_types::protocol::v7::TraceContext;
9+
810
use crate::performance::TransactionOrSpan;
911
use crate::protocol::{Attachment, Breadcrumb, Context, Event, Level, Transaction, User, Value};
1012
#[cfg(feature = "release-health")]
1113
use crate::session::Session;
12-
use crate::Client;
14+
use crate::{Client, SentryTrace, TraceHeader, TraceHeadersIter};
1315

1416
#[derive(Debug)]
1517
pub struct Stack {
@@ -52,6 +54,7 @@ pub struct Scope {
5254
pub(crate) session: Arc<Mutex<Option<Session>>>,
5355
pub(crate) span: Arc<Option<TransactionOrSpan>>,
5456
pub(crate) attachments: Arc<Vec<Attachment>>,
57+
pub(crate) propagation_context: SentryTrace,
5558
}
5659

5760
impl fmt::Debug for Scope {
@@ -74,6 +77,7 @@ impl fmt::Debug for Scope {
7477
debug_struct
7578
.field("span", &self.span)
7679
.field("attachments", &self.attachments.len())
80+
.field("propagation_context", &self.propagation_context)
7781
.finish()
7882
}
7983
}
@@ -289,6 +293,8 @@ impl Scope {
289293

290294
if let Some(span) = self.span.as_ref() {
291295
span.apply_to_event(&mut event);
296+
} else {
297+
self.apply_propagation_context(&mut event);
292298
}
293299

294300
if event.transaction.is_none() {
@@ -357,4 +363,31 @@ impl Scope {
357363
session.update_from_event(event);
358364
}
359365
}
366+
367+
pub(crate) fn apply_propagation_context(&self, event: &mut Event<'_>) {
368+
if event.contexts.contains_key("trace") {
369+
return;
370+
}
371+
372+
let context = TraceContext {
373+
trace_id: self.propagation_context.trace_id,
374+
span_id: self.propagation_context.span_id,
375+
..Default::default()
376+
};
377+
event.contexts.insert("trace".into(), context.into());
378+
}
379+
380+
/// Returns the headers needed for distributed tracing.
381+
pub fn iter_trace_propagation_headers(&self) -> impl Iterator<Item = TraceHeader> {
382+
if let Some(span) = self.get_span() {
383+
span.iter_headers()
384+
} else {
385+
let data = SentryTrace::new(
386+
self.propagation_context.trace_id,
387+
self.propagation_context.span_id,
388+
None,
389+
);
390+
TraceHeadersIter::new(data.to_string())
391+
}
392+
}
360393
}

sentry-types/src/protocol/v7.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,7 +1352,7 @@ pub struct OtelContext {
13521352
}
13531353

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

@@ -1368,6 +1368,12 @@ impl fmt::Display for SpanId {
13681368
}
13691369
}
13701370

1371+
impl fmt::Debug for SpanId {
1372+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1373+
write!(fmt, "SpanId({})", self)
1374+
}
1375+
}
1376+
13711377
impl From<SpanId> for String {
13721378
fn from(span_id: SpanId) -> Self {
13731379
span_id.to_string()
@@ -1399,7 +1405,7 @@ impl From<[u8; 8]> for SpanId {
13991405
}
14001406

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

@@ -1415,6 +1421,12 @@ impl fmt::Display for TraceId {
14151421
}
14161422
}
14171423

1424+
impl fmt::Debug for TraceId {
1425+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1426+
write!(fmt, "TraceId({})", self)
1427+
}
1428+
}
1429+
14181430
impl From<TraceId> for String {
14191431
fn from(trace_id: TraceId) -> Self {
14201432
trace_id.to_string()

sentry/tests/test_basic.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::sync::atomic::{AtomicUsize, Ordering};
44
use std::sync::Arc;
55

6-
use sentry::protocol::{Attachment, EnvelopeItem};
6+
use sentry::protocol::{Attachment, Context, EnvelopeItem};
77
use sentry::types::Uuid;
88

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

31+
#[test]
32+
fn test_event_trace_context_from_propagation_context() {
33+
let mut last_event_id = None::<Uuid>;
34+
let mut span = None;
35+
let events = sentry::test::with_captured_events(|| {
36+
sentry::configure_scope(|scope| {
37+
span = scope.get_span();
38+
});
39+
sentry::capture_message("Hello World!", sentry::Level::Warning);
40+
last_event_id = sentry::last_event_id();
41+
});
42+
assert_eq!(events.len(), 1);
43+
let event = events.into_iter().next().unwrap();
44+
45+
let trace_context = event.contexts.get("trace");
46+
assert!(span.is_none());
47+
assert!(matches!(trace_context, Some(Context::Trace(_))));
48+
}
49+
3150
#[test]
3251
fn test_breadcrumbs() {
3352
let events = sentry::test::with_captured_events(|| {

0 commit comments

Comments
 (0)