-
-
Notifications
You must be signed in to change notification settings - Fork 166
Add experimental metrics implementation #618
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
9f33090
Add experimental metrics implementation
Swatinem c83b9aa
finish up impl
Swatinem dd22afb
implement sets the naive way
Swatinem 2fc55ae
do not emit an empty envelope item on shutdown
Swatinem 97c9a0c
review
Swatinem b6bdece
add rate limit category
Swatinem 331a457
fix typo
Swatinem a61de48
fix build
Swatinem 06c483a
try to fix flush race on windows
Swatinem c692048
fix doc builds
Swatinem b568ebc
remove forced shutdown from metrics aggregator
Swatinem 255c578
Merge branch 'master' into swatinem/metrics
jan-auer dc5ea74
ref(metrics): Minor review comments
jan-auer f2c6410
ref: Separate cadence from metrics feature
jan-auer 45a4ac1
ref(metrics): Minor code-level changes
jan-auer d24c9b2
ref(metrics): Refactor to match Python SDK
jan-auer b515db3
fix: First bugfixes
jan-auer 1267668
fix: Flakey submission
jan-auer c164983
feat(metrics): Add a convenience API to track metrics directly
jan-auer ba4afe0
fix: Move statsd parsing to metrics module
jan-auer bcc665b
ref(metrics): Reorganize and add docs
jan-auer 32e6baa
test: Add a first unit test
jan-auer 0ef19a3
feat(metrics): Inject default tags
jan-auer 12bf258
ref(metrics): Simplify unit handling
jan-auer d90d14c
feat(metrics): Further improvements to docs and cadence
jan-auer 524a5a9
fix(metrics): Sanitation behavior
jan-auer c506251
fix: Docs
jan-auer c3f7247
ref(metrics): Refactor worker into separate struct
jan-auer edbbb95
ref(metrics): Add more Debug impls
jan-auer 55b7f28
ref: Rename metric item to "statsd"
jan-auer ab14afa
ref(metrics): Flush synchronously
jan-auer 0c4064c
ref: Address review feedback
jan-auer 139cdf3
ref(metrics): Reduce duplication of bucket key
jan-auer 69080ab
meta: Changelog
jan-auer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
//! [`cadence`] integration for Sentry. | ||
//! | ||
//! [`cadence`] is a popular Statsd client for Rust. The [`SentryMetricSink`] provides a drop-in | ||
//! integration to send metrics captured via `cadence` to Sentry. For direct usage of Sentry | ||
//! metrics, see the [`metrics`](crate::metrics) module. | ||
//! | ||
//! # Usage | ||
//! | ||
//! To use the `cadence` integration, enable the `UNSTABLE_cadence` feature in your `Cargo.toml`. | ||
//! Then, create a [`SentryMetricSink`] and pass it to your `cadence` client: | ||
//! | ||
//! ``` | ||
//! use cadence::StatsdClient; | ||
//! use sentry::cadence::SentryMetricSink; | ||
//! | ||
//! let client = StatsdClient::from_sink("sentry.test", SentryMetricSink::new()); | ||
//! ``` | ||
//! | ||
//! # Side-by-side Usage | ||
//! | ||
//! If you want to send metrics to Sentry and another backend at the same time, you can use | ||
//! [`SentryMetricSink::wrap`] to wrap another [`MetricSink`]: | ||
//! | ||
//! ``` | ||
//! use cadence::{StatsdClient, NopMetricSink}; | ||
//! use sentry::cadence::SentryMetricSink; | ||
//! | ||
//! let sink = SentryMetricSink::wrap(NopMetricSink); | ||
//! let client = StatsdClient::from_sink("sentry.test", sink); | ||
//! ``` | ||
|
||
use std::sync::Arc; | ||
|
||
use cadence::{MetricSink, NopMetricSink}; | ||
|
||
use crate::metrics::Metric; | ||
use crate::{Client, Hub}; | ||
|
||
/// A [`MetricSink`] that sends metrics to Sentry. | ||
/// | ||
/// This metric sends all metrics to Sentry. The Sentry client is internally buffered, so submission | ||
/// will be delayed. | ||
/// | ||
/// Optionally, this sink can also forward metrics to another [`MetricSink`]. This is useful if you | ||
/// want to send metrics to Sentry and another backend at the same time. Use | ||
/// [`SentryMetricSink::wrap`] to construct such a sink. | ||
#[derive(Debug)] | ||
pub struct SentryMetricSink<S = NopMetricSink> { | ||
client: Option<Arc<Client>>, | ||
sink: S, | ||
} | ||
|
||
impl<S> SentryMetricSink<S> | ||
where | ||
S: MetricSink, | ||
{ | ||
/// Creates a new [`SentryMetricSink`], wrapping the given [`MetricSink`]. | ||
pub fn wrap(sink: S) -> Self { | ||
Self { client: None, sink } | ||
} | ||
|
||
/// Creates a new [`SentryMetricSink`] sending data to the given [`Client`]. | ||
pub fn with_client(mut self, client: Arc<Client>) -> Self { | ||
self.client = Some(client); | ||
self | ||
} | ||
} | ||
|
||
impl SentryMetricSink { | ||
/// Creates a new [`SentryMetricSink`]. | ||
/// | ||
/// It is not required that a client is available when this sink is created. The sink sends | ||
/// metrics to the client of the Sentry hub that is registered when the metrics are emitted. | ||
pub fn new() -> Self { | ||
Self { | ||
client: None, | ||
sink: NopMetricSink, | ||
} | ||
} | ||
} | ||
|
||
impl Default for SentryMetricSink { | ||
fn default() -> Self { | ||
Self::new() | ||
} | ||
} | ||
|
||
impl MetricSink for SentryMetricSink { | ||
fn emit(&self, string: &str) -> std::io::Result<usize> { | ||
if let Ok(metric) = Metric::parse_statsd(string) { | ||
if let Some(ref client) = self.client { | ||
client.add_metric(metric); | ||
} else if let Some(client) = Hub::current().client() { | ||
client.add_metric(metric); | ||
} | ||
} | ||
|
||
// NopMetricSink returns `0`, which is correct as Sentry is buffering the metrics. | ||
self.sink.emit(string) | ||
} | ||
|
||
fn flush(&self) -> std::io::Result<()> { | ||
let flushed = if let Some(ref client) = self.client { | ||
client.flush(None) | ||
} else if let Some(client) = Hub::current().client() { | ||
client.flush(None) | ||
} else { | ||
true | ||
}; | ||
|
||
let sink_result = self.sink.flush(); | ||
|
||
if !flushed { | ||
Err(std::io::Error::new( | ||
std::io::ErrorKind::Other, | ||
"failed to flush metrics to Sentry", | ||
)) | ||
} else { | ||
sink_result | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use cadence::{Counted, Distributed}; | ||
use sentry_types::protocol::latest::EnvelopeItem; | ||
|
||
use crate::test::with_captured_envelopes; | ||
|
||
use super::*; | ||
|
||
#[test] | ||
fn test_basic_metrics() { | ||
let envelopes = with_captured_envelopes(|| { | ||
let client = cadence::StatsdClient::from_sink("sentry.test", SentryMetricSink::new()); | ||
client.count("some.count", 1).unwrap(); | ||
client.count("some.count", 10).unwrap(); | ||
client | ||
.count_with_tags("count.with.tags", 1) | ||
.with_tag("foo", "bar") | ||
.send(); | ||
client.distribution("some.distr", 1).unwrap(); | ||
client.distribution("some.distr", 2).unwrap(); | ||
client.distribution("some.distr", 3).unwrap(); | ||
}); | ||
assert_eq!(envelopes.len(), 1); | ||
|
||
let mut items = envelopes[0].items(); | ||
let Some(EnvelopeItem::Statsd(metrics)) = items.next() else { | ||
panic!("expected metrics"); | ||
}; | ||
let metrics = std::str::from_utf8(metrics).unwrap(); | ||
|
||
println!("{metrics}"); | ||
|
||
assert!(metrics.contains("sentry.test.count.with.tags:1|c|#foo:bar|T")); | ||
assert!(metrics.contains("sentry.test.some.count:11|c|T")); | ||
assert!(metrics.contains("sentry.test.some.distr:1:2:3|d|T")); | ||
assert_eq!(items.next(), None); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.