Skip to content

Commit d6f79ef

Browse files
feat(sentry-core): Implement trace metric capture batching (#1026)
Basic metrics capture functionality. Follow-up PR will implement the rest. Closes #1023 Closes [RUST-168](https://linear.app/getsentry/issue/RUST-168/implement-trace-metric-capture-and-batching-in-sentry-core)
1 parent d4440fb commit d6f79ef

File tree

9 files changed

+356
-3
lines changed

9 files changed

+356
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Add SDK protocol support for sending `trace_metric` envelope items ([#1022](https://github.com/getsentry/sentry-rust/pull/1022)).
88
- Add `Metric` and `MetricType` types representing [trace metrics](https://develop.sentry.dev/sdk/telemetry/metrics/) ([#1026](https://github.com/getsentry/sentry-rust/pull/1026)).
9+
- Add metric capture and batching in `sentry-core`. Metrics can be captured via `Hub::capture_metric` and are batched and sent as `trace_metric` envelope items. Controlled by the `metrics` feature flag and `ClientOptions::enable_metrics` ([#1026](https://github.com/getsentry/sentry-rust/pull/1026)).
910

1011
## 0.47.0
1112

sentry-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ client = ["rand"]
2525
test = ["client", "release-health"]
2626
release-health = []
2727
logs = []
28+
metrics = []
2829

2930
[dependencies]
3031
log = { version = "0.4.8", optional = true, features = ["std"] }

sentry-core/src/batcher.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use crate::client::TransportArc;
88
use crate::protocol::EnvelopeItem;
99
use crate::Envelope;
1010
use sentry_types::protocol::v7::Log;
11+
#[cfg(feature = "metrics")]
12+
use sentry_types::protocol::v7::Metric;
1113

1214
// Flush when there's 100 items in the buffer
1315
const MAX_ITEMS: usize = 100;
@@ -40,6 +42,11 @@ impl Batch for Log {
4042
const TYPE_NAME: &str = "logs";
4143
}
4244

45+
#[cfg(feature = "metrics")]
46+
impl Batch for Metric {
47+
const TYPE_NAME: &str = "metrics";
48+
}
49+
4350
/// Accumulates items in the queue and submits them through the transport when one of the flushing
4451
/// conditions is met.
4552
pub(crate) struct Batcher<T: Batch> {
@@ -154,7 +161,7 @@ impl<T: Batch> Drop for Batcher<T> {
154161
}
155162
}
156163

157-
#[cfg(all(test, feature = "test"))]
164+
#[cfg(all(test, feature = "test", feature = "logs"))]
158165
mod tests {
159166
use crate::logger_info;
160167
use crate::test;

sentry-core/src/client.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::protocol::SessionUpdate;
1212
use rand::random;
1313
use sentry_types::random_uuid;
1414

15-
#[cfg(feature = "logs")]
15+
#[cfg(any(feature = "logs", feature = "metrics"))]
1616
use crate::batcher::Batcher;
1717
use crate::constants::SDK_INFO;
1818
use crate::protocol::{ClientSdkInfo, Event};
@@ -24,6 +24,8 @@ use crate::SessionMode;
2424
use crate::{ClientOptions, Envelope, Hub, Integration, Scope, Transport};
2525
#[cfg(feature = "logs")]
2626
use sentry_types::protocol::v7::Context;
27+
#[cfg(feature = "metrics")]
28+
use sentry_types::protocol::v7::Metric;
2729
#[cfg(feature = "logs")]
2830
use sentry_types::protocol::v7::{Log, LogAttribute};
2931

@@ -59,6 +61,8 @@ pub struct Client {
5961
session_flusher: RwLock<Option<SessionFlusher>>,
6062
#[cfg(feature = "logs")]
6163
logs_batcher: RwLock<Option<Batcher<Log>>>,
64+
#[cfg(feature = "metrics")]
65+
metrics_batcher: RwLock<Option<Batcher<Metric>>>,
6266
#[cfg(feature = "logs")]
6367
default_log_attributes: Option<BTreeMap<String, LogAttribute>>,
6468
integrations: Vec<(TypeId, Arc<dyn Integration>)>,
@@ -91,13 +95,22 @@ impl Clone for Client {
9195
None
9296
});
9397

98+
#[cfg(feature = "metrics")]
99+
let metrics_batcher = RwLock::new(if self.options.enable_metrics {
100+
Some(Batcher::new(transport.clone()))
101+
} else {
102+
None
103+
});
104+
94105
Client {
95106
options: self.options.clone(),
96107
transport,
97108
#[cfg(feature = "release-health")]
98109
session_flusher,
99110
#[cfg(feature = "logs")]
100111
logs_batcher,
112+
#[cfg(feature = "metrics")]
113+
metrics_batcher,
101114
#[cfg(feature = "logs")]
102115
default_log_attributes: self.default_log_attributes.clone(),
103116
integrations: self.integrations.clone(),
@@ -176,6 +189,13 @@ impl Client {
176189
None
177190
});
178191

192+
#[cfg(feature = "metrics")]
193+
let metrics_batcher = RwLock::new(if options.enable_metrics {
194+
Some(Batcher::new(transport.clone()))
195+
} else {
196+
None
197+
});
198+
179199
#[allow(unused_mut)]
180200
let mut client = Client {
181201
options,
@@ -184,6 +204,8 @@ impl Client {
184204
session_flusher,
185205
#[cfg(feature = "logs")]
186206
logs_batcher,
207+
#[cfg(feature = "metrics")]
208+
metrics_batcher,
187209
#[cfg(feature = "logs")]
188210
default_log_attributes: None,
189211
integrations,
@@ -420,6 +442,10 @@ impl Client {
420442
if let Some(ref batcher) = *self.logs_batcher.read().unwrap() {
421443
batcher.flush();
422444
}
445+
#[cfg(feature = "metrics")]
446+
if let Some(ref batcher) = *self.metrics_batcher.read().unwrap() {
447+
batcher.flush();
448+
}
423449
if let Some(ref transport) = *self.transport.read().unwrap() {
424450
transport.flush(timeout.unwrap_or(self.options.shutdown_timeout))
425451
} else {
@@ -439,6 +465,8 @@ impl Client {
439465
drop(self.session_flusher.write().unwrap().take());
440466
#[cfg(feature = "logs")]
441467
drop(self.logs_batcher.write().unwrap().take());
468+
#[cfg(feature = "metrics")]
469+
drop(self.metrics_batcher.write().unwrap().take());
442470
let transport_opt = self.transport.write().unwrap().take();
443471
if let Some(transport) = transport_opt {
444472
sentry_debug!("client close; request transport to shut down");
@@ -493,6 +521,20 @@ impl Client {
493521

494522
Some(log)
495523
}
524+
525+
/// Captures a metric and sends it to Sentry.
526+
#[cfg(feature = "metrics")]
527+
pub fn capture_metric(&self, metric: Metric, _: &Scope) {
528+
// TODO: Read 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+
}
537+
}
496538
}
497539

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

sentry-core/src/clientoptions.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ pub struct ClientOptions {
172172
/// Determines whether captured structured logs should be sent to Sentry (defaults to false).
173173
#[cfg(feature = "logs")]
174174
pub enable_logs: bool,
175+
/// Determines whether captured metrics should be sent to Sentry (defaults to true).
176+
#[cfg(feature = "metrics")]
177+
pub enable_metrics: bool,
175178
// Other options not documented in Unified API
176179
/// Disable SSL verification.
177180
///
@@ -278,6 +281,9 @@ impl fmt::Debug for ClientOptions {
278281
.field("enable_logs", &self.enable_logs)
279282
.field("before_send_log", &before_send_log);
280283

284+
#[cfg(feature = "metrics")]
285+
debug_struct.field("enable_metrics", &self.enable_metrics);
286+
281287
debug_struct.field("user_agent", &self.user_agent).finish()
282288
}
283289
}
@@ -317,6 +323,8 @@ impl Default for ClientOptions {
317323
enable_logs: true,
318324
#[cfg(feature = "logs")]
319325
before_send_log: None,
326+
#[cfg(feature = "metrics")]
327+
enable_metrics: false,
320328
}
321329
}
322330
}

sentry-core/src/hub.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use std::sync::{Arc, RwLock};
66

7+
#[cfg(feature = "metrics")]
8+
use crate::protocol::Metric;
79
use crate::protocol::{Event, Level, Log, LogAttribute, LogLevel, Map, SessionStatus};
810
use crate::types::Uuid;
911
use crate::{Integration, IntoBreadcrumbs, Scope, ScopeGuard};
@@ -255,4 +257,14 @@ impl Hub {
255257
client.capture_log(log, &top.scope);
256258
}}
257259
}
260+
261+
/// Captures a metric.
262+
#[cfg(feature = "metrics")]
263+
pub fn capture_metric(&self, metric: Metric) {
264+
with_client_impl! {{
265+
let top = self.inner.with(|stack| stack.top().clone());
266+
let Some(ref client) = top.client else { return };
267+
client.capture_metric(metric, &top.scope);
268+
}}
269+
}
258270
}

sentry-core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ pub use crate::transport::{Transport, TransportFactory};
136136
mod logger; // structured logging macros exported with `#[macro_export]`
137137

138138
// client feature
139-
#[cfg(all(feature = "client", feature = "logs"))]
139+
#[cfg(all(feature = "client", any(feature = "logs", feature = "metrics")))]
140140
mod batcher;
141141
#[cfg(feature = "client")]
142142
mod client;

0 commit comments

Comments
 (0)