Skip to content

Commit d425a32

Browse files
feat(metrics): Associate trace data with metrics (RUST-186)
Prepare metrics through scope trace association before enqueueing them so metric capture inherits the current tracing context. Set metric trace_id from the active span when present and otherwise from the propagation context, and set span_id from the active span. Keep the matching no-op scope API and the propagation-context metric test, while excluding the default attribute, user enrichment, and callback changes from [#1031](#1031). Co-authored-by: Joris Bayer <joris.bayer@sentry.io> Closes #1058 Closes [RUST-186](https://linear.app/getsentry/issue/RUST-186/add-trace-metric-tracing-association-in-sentry-core)
1 parent 21cd8f2 commit d425a32

4 files changed

Lines changed: 68 additions & 8 deletions

File tree

sentry-core/src/client.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -524,16 +524,25 @@ impl Client {
524524

525525
/// Captures a metric and sends it to Sentry.
526526
#[cfg(feature = "metrics")]
527-
pub fn capture_metric(&self, metric: Metric, _: &Scope) {
528-
if let Some(batcher) = self
529-
.metrics_batcher
530-
.read()
531-
.expect("metrics batcher lock could not be acquired")
532-
.as_ref()
533-
{
534-
batcher.enqueue(metric);
527+
pub fn capture_metric(&self, metric: Metric, scope: &Scope) {
528+
if let Some(metric) = self.prepare_metric(metric, scope) {
529+
if let Some(batcher) = self
530+
.metrics_batcher
531+
.read()
532+
.expect("metrics batcher lock could not be acquired")
533+
.as_ref()
534+
{
535+
batcher.enqueue(metric);
536+
}
535537
}
536538
}
539+
540+
/// Prepares a metric to be sent, setting trace association data from the scope.
541+
#[cfg(feature = "metrics")]
542+
fn prepare_metric(&self, mut metric: Metric, scope: &Scope) -> Option<Metric> {
543+
scope.apply_to_metric(&mut metric);
544+
Some(metric)
545+
}
537546
}
538547

539548
// Make this unwind safe. It's not out of the box because of the

sentry-core/src/scope/noop.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use std::fmt;
22

33
#[cfg(feature = "logs")]
44
use crate::protocol::Log;
5+
#[cfg(feature = "metrics")]
6+
use crate::protocol::Metric;
57
use crate::protocol::{Context, Event, Level, User, Value};
68
use crate::TransactionOrSpan;
79

@@ -119,6 +121,13 @@ impl Scope {
119121
minimal_unreachable!();
120122
}
121123

124+
/// Applies the contained scoped data to fill a trace metric.
125+
#[cfg(feature = "metrics")]
126+
pub fn apply_to_metric(&self, metric: &mut Metric) {
127+
let _metric = metric;
128+
minimal_unreachable!();
129+
}
130+
122131
/// Set the given [`TransactionOrSpan`] as the active span for this scope.
123132
pub fn set_span(&mut self, span: Option<TransactionOrSpan>) {
124133
let _ = span;

sentry-core/src/scope/real.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use crate::protocol::{
1111
};
1212
#[cfg(feature = "logs")]
1313
use crate::protocol::{Log, LogAttribute};
14+
#[cfg(feature = "metrics")]
15+
use crate::protocol::Metric;
1416
#[cfg(feature = "release-health")]
1517
use crate::session::Session;
1618
use crate::{Client, SentryTrace, TraceHeader, TraceHeadersIter};
@@ -399,6 +401,20 @@ impl Scope {
399401
}
400402
}
401403

404+
/// Applies the contained scoped data to a trace metric, setting the `trace_id` and `span_id`.
405+
#[cfg(feature = "metrics")]
406+
pub fn apply_to_metric(&self, metric: &mut Metric) {
407+
metric.trace_id = self
408+
.get_span()
409+
.map(|span| span.get_trace_context().trace_id)
410+
.unwrap_or(self.propagation_context.trace_id);
411+
412+
metric.span_id = self.get_span().map(|span| match span {
413+
crate::TransactionOrSpan::Transaction(transaction) => transaction.get_trace_context().span_id,
414+
crate::TransactionOrSpan::Span(span) => span.get_span_id(),
415+
});
416+
}
417+
402418
/// Set the given [`TransactionOrSpan`] as the active span for this scope.
403419
pub fn set_span(&mut self, span: Option<TransactionOrSpan>) {
404420
self.span = Arc::new(span);

sentry-core/tests/metrics.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,32 @@ fn test_metrics_batching_over_limit() {
204204
)
205205
}
206206

207+
/// Test that trace_id is set from the propagation context when no span is active.
208+
#[test]
209+
fn metrics_trace_id_from_propagation_context() {
210+
let options = ClientOptions {
211+
enable_metrics: true,
212+
..Default::default()
213+
};
214+
215+
let envelopes = test::with_captured_envelopes_options(|| capture_test_metric("test"), options);
216+
let envelope = envelopes
217+
.try_into_only_item()
218+
.expect("expected one envelope");
219+
let item = envelope
220+
.into_items()
221+
.try_into_only_item()
222+
.expect("expected one item");
223+
let mut metrics = item.into_metrics().expect("expected metrics item");
224+
let metric = metrics.pop().expect("expected one metric");
225+
226+
assert_ne!(
227+
metric.trace_id,
228+
Default::default(),
229+
"trace_id should be set from propagation context"
230+
);
231+
}
232+
207233
/// Returns a [`Metric`] with [type `Counter`](MetricType),
208234
/// the provided name, and a value of `1.0`.
209235
fn test_metric<S>(name: S) -> Metric

0 commit comments

Comments
 (0)