use std::sync::Arc;
use cadence::{MetricSink, NopMetricSink};
use crate::metrics::Metric;
use crate::{Client, Hub};
#[derive(Debug)]
pub struct SentryMetricSink<S = NopMetricSink> {
client: Option<Arc<Client>>,
sink: S,
}
impl<S> SentryMetricSink<S>
where
S: MetricSink,
{
pub fn wrap(sink: S) -> Self {
Self { client: None, sink }
}
pub fn with_client(mut self, client: Arc<Client>) -> Self {
self.client = Some(client);
self
}
}
impl SentryMetricSink {
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);
}
}
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);
}
}