Repository: getsentry/sentry-rust
Pull request #822 (2025-05-27 13:09:15)

fix: fix clippy lints on stable
Fixes new clippy lints.

#skip-changelog

github-actions[bot]: 
<!--
  1 failure:  Please consider a...
  0 warning: 
  
  1 markdown notices
  DangerID: danger-id-Danger;
-->

<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true">Fails</th>
    </tr>
  </thead>
  <tbody><tr>
      <td>:no_entry_sign:</td>
      <td>Please consider adding a changelog entry for the next release.</td>
    </tr>
  </tbody>
</table>



### Instructions and example for changelog

Please add an entry to `CHANGELOG.md` to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

```markdown
## Unreleased

- fix clippy lints on stable ([#822](https://github.com/getsentry/sentry-rust/pull/822))
```

If none of the above apply, you can opt out of this check by adding `#skip-changelog` to the PR description.
<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/js">dangerJS</a> against 12bb929894fb60911b8e6220ce4a025e0b306429
</p>

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getsentry/sentry-rust/pull/822?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 73.05%. Comparing base [(`ae9779f`)](https://app.codecov.io/gh/getsentry/sentry-rust/commit/ae9779f9a9dd107e6ec9b2205f5929911eb05b03?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry) to head [(`12bb929`)](https://app.codecov.io/gh/getsentry/sentry-rust/commit/12bb929894fb60911b8e6220ce4a025e0b306429?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master     #822   +/-   ##
=======================================
  Coverage   73.05%   73.05%           
=======================================
  Files          63       63           
  Lines        7267     7267           
=======================================
  Hits         5309     5309           
  Misses       1958     1958           
```

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa586ef..ba719c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
 
diff --git a/sentry-actix/examples/basic.rs b/sentry-actix/examples/basic.rs
index 6e4544f..acde856 100644
--- a/sentry-actix/examples/basic.rs
+++ b/sentry-actix/examples/basic.rs
@@ -11,7 +11,7 @@ async fn healthy(_req: HttpRequest) -> Result<String, Error> {
 
 #[get("/err")]
 async fn errors(_req: HttpRequest) -> Result<String, Error> {
-    Err(io::Error::new(io::ErrorKind::Other, "An error happens here").into())
+    Err(io::Error::other("An error happens here").into())
 }
 
 #[get("/msg")]
diff --git a/sentry-actix/src/lib.rs b/sentry-actix/src/lib.rs
index 78938d5..4a2de94 100644
--- a/sentry-actix/src/lib.rs
+++ b/sentry-actix/src/lib.rs
@@ -589,7 +589,7 @@ mod tests {
                     // Current hub should have no events
                     _assert_hub_no_events();
 
-                    Err(io::Error::new(io::ErrorKind::Other, "Test Error").into())
+                    Err(io::Error::other("Test Error").into())
                 }
 
                 let app = init_service(
@@ -652,7 +652,7 @@ mod tests {
                 async fn original_transaction(_req: HttpRequest) -> Result<String, Error> {
                     // Override transaction name
                     sentry::configure_scope(|scope| scope.set_transaction(Some("new_transaction")));
-                    Err(io::Error::new(io::ErrorKind::Other, "Test Error").into())
+                    Err(io::Error::other("Test Error").into())
                 }
 
                 let app = init_service(
diff --git a/sentry-core/src/performance.rs b/sentry-core/src/performance.rs
index f36ed6b..39ee236 100644
--- a/sentry-core/src/performance.rs
+++ b/sentry-core/src/performance.rs
@@ -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,
         }
@@ -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(),
@@ -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),
@@ -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()),
         }
@@ -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
@@ -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);
 
@@ -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
@@ -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,
+        }
     }
 }
 
@@ -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),
@@ -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(())
@@ -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));
     }
@@ -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]
diff --git a/sentry-core/src/scope/real.rs b/sentry-core/src/scope/real.rs
index 7c4eda4..47b1e9d 100644
--- a/sentry-core/src/scope/real.rs
+++ b/sentry-core/src/scope/real.rs
@@ -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 {
@@ -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 {
@@ -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()
     }
 }
@@ -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() {
@@ -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())
+        }
+    }
 }
diff --git a/sentry-types/src/protocol/v7.rs b/sentry-types/src/protocol/v7.rs
index bb2008f..12de863 100644
--- a/sentry-types/src/protocol/v7.rs
+++ b/sentry-types/src/protocol/v7.rs
@@ -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]);
 
@@ -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()
@@ -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]);
 
@@ -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()


Test patch:
diff --git a/sentry/tests/test_basic.rs b/sentry/tests/test_basic.rs
index 3813b14..dac87e3 100644
--- a/sentry/tests/test_basic.rs
+++ b/sentry/tests/test_basic.rs
@@ -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]
@@ -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(|| {