Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion sentry-core/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,8 @@ impl Client {
}
}

/// Prepares a metric to be sent, setting trace association data and default attributes.
/// Prepares a metric to be sent, setting the `trace_id` and other default attributes, and
/// processing it through `before_send_metric`.
#[cfg(feature = "metrics")]
fn prepare_metric(&self, mut metric: Metric, scope: &Scope) -> Option<Metric> {
scope.apply_to_metric(&mut metric, self.options.send_default_pii);
Expand All @@ -579,6 +580,10 @@ impl Client {
metric.attributes.entry(key.clone()).or_insert(val.clone());
}

if let Some(ref func) = self.options.before_send_metric {
metric = func(metric)?;
}

Some(metric)
}
}
Expand Down
17 changes: 16 additions & 1 deletion sentry-core/src/clientoptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::constants::USER_AGENT;
use crate::performance::TracesSampler;
#[cfg(feature = "logs")]
use crate::protocol::Log;
#[cfg(feature = "metrics")]
use crate::protocol::Metric;
use crate::protocol::{Breadcrumb, Event};
use crate::types::Dsn;
use crate::{Integration, IntoDsn, TransportFactory};
Expand Down Expand Up @@ -175,6 +177,9 @@ pub struct ClientOptions {
/// Determines whether captured metrics should be sent to Sentry (defaults to false).
#[cfg(feature = "metrics")]
pub enable_metrics: bool,
/// Callback that is executed for each Metric before sending.
#[cfg(feature = "metrics")]
pub before_send_metric: Option<BeforeCallback<Metric>>,
// Other options not documented in Unified API
/// Disable SSL verification.
///
Expand Down Expand Up @@ -235,6 +240,12 @@ impl fmt::Debug for ClientOptions {
struct BeforeSendLog;
self.before_send_log.as_ref().map(|_| BeforeSendLog)
};
#[cfg(feature = "metrics")]
let before_send_metric = {
#[derive(Debug)]
struct BeforeSendMetric;
self.before_send_metric.as_ref().map(|_| BeforeSendMetric)
};
#[derive(Debug)]
struct TransportFactory;

Expand Down Expand Up @@ -282,7 +293,9 @@ impl fmt::Debug for ClientOptions {
.field("before_send_log", &before_send_log);

#[cfg(feature = "metrics")]
debug_struct.field("enable_metrics", &self.enable_metrics);
debug_struct
.field("enable_metrics", &self.enable_metrics)
.field("before_send_metric", &before_send_metric);

debug_struct.field("user_agent", &self.user_agent).finish()
}
Expand Down Expand Up @@ -325,6 +338,8 @@ impl Default for ClientOptions {
before_send_log: None,
#[cfg(feature = "metrics")]
enable_metrics: false,
#[cfg(feature = "metrics")]
before_send_metric: None,
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions sentry-core/tests/metrics.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![cfg(all(feature = "test", feature = "metrics"))]

use std::collections::HashSet;
use std::sync::Arc;
use std::time::SystemTime;

use anyhow::{Context, Result};
Expand Down Expand Up @@ -494,6 +495,45 @@ fn metric_user_attributes_do_not_overwrite_explicit() {
assert_eq!(metric.attributes, expected_attributes);
}

/// Test that `before_send_metric` can filter out metrics.
#[test]
fn before_send_metric_can_drop() {
let options = ClientOptions {
enable_metrics: true,
before_send_metric: Some(Arc::new(|_| None)),
..Default::default()
};

let envelopes = test::with_captured_envelopes_options(|| capture_test_metric("test"), options);
assert!(
envelopes.is_empty(),
"metric should be dropped by before_send_metric"
);
}

/// Test that `before_send_metric` can modify metrics.
#[test]
fn before_send_metric_can_modify() {
let options = ClientOptions {
enable_metrics: true,
before_send_metric: Some(Arc::new(|mut metric| {
metric
.attributes
.insert("added_by_callback".into(), LogAttribute(Value::from("yes")));
Some(metric)
})),
..Default::default()
};

let envelopes = test::with_captured_envelopes_options(|| capture_test_metric("test"), options);
let metric = extract_single_metric(envelopes).expect("expected a single-metric envelope");

assert_eq!(
metric.attributes.get("added_by_callback"),
Some(&LogAttribute(Value::from("yes"))),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we intend to call this just Attribute (could either be a new type or just a type alias)? I don't remember what was the outcome of that discussion (if any).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think it makes sense to make that change. Likely we should do it as a type alias (rename LogAttribute to Attribute, and make LogAttribute an alias to Attribute). However, I believe we can also do this later, should be a backwards and forwards-compatible change as far as I am aware

);
}

/// Returns a [`Metric`] with [type `Counter`](MetricType),
/// the provided name, and a value of `1.0`.
fn test_metric<S>(name: S) -> Metric
Expand Down
Loading