Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Add SDK protocol support for sending `trace_metric` envelope items ([#1022](https://github.com/getsentry/sentry-rust/pull/1022)).
- Add `Metric` and `MetricType` types representing [trace metrics](https://develop.sentry.dev/sdk/telemetry/metrics/) ([#1026](https://github.com/getsentry/sentry-rust/pull/1026)).
- 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)).

## 0.47.0

Expand Down
1 change: 1 addition & 0 deletions sentry-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ client = ["rand"]
test = ["client", "release-health"]
release-health = []
logs = []
metrics = []

[dependencies]
log = { version = "0.4.8", optional = true, features = ["std"] }
Expand Down
9 changes: 8 additions & 1 deletion sentry-core/src/batcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use crate::client::TransportArc;
use crate::protocol::EnvelopeItem;
use crate::Envelope;
use sentry_types::protocol::v7::Log;
#[cfg(feature = "metrics")]
use sentry_types::protocol::v7::Metric;

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

#[cfg(feature = "metrics")]
impl Batch for Metric {
const TYPE_NAME: &str = "metrics";
}

/// Accumulates items in the queue and submits them through the transport when one of the flushing
/// conditions is met.
pub(crate) struct Batcher<T: Batch> {
Expand Down Expand Up @@ -154,7 +161,7 @@ impl<T: Batch> Drop for Batcher<T> {
}
}

#[cfg(all(test, feature = "test"))]
#[cfg(all(test, feature = "test", feature = "logs"))]
mod tests {
use crate::logger_info;
use crate::test;
Expand Down
44 changes: 43 additions & 1 deletion sentry-core/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::protocol::SessionUpdate;
use rand::random;
use sentry_types::random_uuid;

#[cfg(feature = "logs")]
#[cfg(any(feature = "logs", feature = "metrics"))]
use crate::batcher::Batcher;
use crate::constants::SDK_INFO;
use crate::protocol::{ClientSdkInfo, Event};
Expand All @@ -24,6 +24,8 @@ use crate::SessionMode;
use crate::{ClientOptions, Envelope, Hub, Integration, Scope, Transport};
#[cfg(feature = "logs")]
use sentry_types::protocol::v7::Context;
#[cfg(feature = "metrics")]
use sentry_types::protocol::v7::Metric;
#[cfg(feature = "logs")]
use sentry_types::protocol::v7::{Log, LogAttribute};

Expand Down Expand Up @@ -59,6 +61,8 @@ pub struct Client {
session_flusher: RwLock<Option<SessionFlusher>>,
#[cfg(feature = "logs")]
logs_batcher: RwLock<Option<Batcher<Log>>>,
#[cfg(feature = "metrics")]
metrics_batcher: RwLock<Option<Batcher<Metric>>>,
#[cfg(feature = "logs")]
default_log_attributes: Option<BTreeMap<String, LogAttribute>>,
integrations: Vec<(TypeId, Arc<dyn Integration>)>,
Expand Down Expand Up @@ -91,13 +95,22 @@ impl Clone for Client {
None
});

#[cfg(feature = "metrics")]
let metrics_batcher = RwLock::new(if self.options.enable_metrics {
Some(Batcher::new(transport.clone()))
} else {
None
});

Client {
options: self.options.clone(),
transport,
#[cfg(feature = "release-health")]
session_flusher,
#[cfg(feature = "logs")]
logs_batcher,
#[cfg(feature = "metrics")]
metrics_batcher,
#[cfg(feature = "logs")]
default_log_attributes: self.default_log_attributes.clone(),
integrations: self.integrations.clone(),
Expand Down Expand Up @@ -176,6 +189,13 @@ impl Client {
None
});

#[cfg(feature = "metrics")]
let metrics_batcher = RwLock::new(if options.enable_metrics {
Some(Batcher::new(transport.clone()))
} else {
None
});

#[allow(unused_mut)]
let mut client = Client {
options,
Expand All @@ -184,6 +204,8 @@ impl Client {
session_flusher,
#[cfg(feature = "logs")]
logs_batcher,
#[cfg(feature = "metrics")]
metrics_batcher,
#[cfg(feature = "logs")]
default_log_attributes: None,
integrations,
Expand Down Expand Up @@ -420,6 +442,10 @@ impl Client {
if let Some(ref batcher) = *self.logs_batcher.read().unwrap() {
batcher.flush();
}
#[cfg(feature = "metrics")]
if let Some(ref batcher) = *self.metrics_batcher.read().unwrap() {
batcher.flush();
}
if let Some(ref transport) = *self.transport.read().unwrap() {
transport.flush(timeout.unwrap_or(self.options.shutdown_timeout))
} else {
Expand All @@ -439,6 +465,8 @@ impl Client {
drop(self.session_flusher.write().unwrap().take());
#[cfg(feature = "logs")]
drop(self.logs_batcher.write().unwrap().take());
#[cfg(feature = "metrics")]
drop(self.metrics_batcher.write().unwrap().take());
let transport_opt = self.transport.write().unwrap().take();
if let Some(transport) = transport_opt {
sentry_debug!("client close; request transport to shut down");
Expand Down Expand Up @@ -493,6 +521,20 @@ impl Client {

Some(log)
}

/// Captures a metric and sends it to Sentry.
#[cfg(feature = "metrics")]
pub fn capture_metric(&self, metric: Metric, _: &Scope) {
// TODO: Read scope
if let Some(batcher) = self
.metrics_batcher
.read()
.expect("metrics batcher lock could not be acquired")
.as_ref()
{
batcher.enqueue(metric);
}
}
}

// Make this unwind safe. It's not out of the box because of the
Expand Down
8 changes: 8 additions & 0 deletions sentry-core/src/clientoptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ pub struct ClientOptions {
/// Determines whether captured structured logs should be sent to Sentry (defaults to false).
#[cfg(feature = "logs")]
pub enable_logs: bool,
/// Determines whether captured metrics should be sent to Sentry (defaults to true).
#[cfg(feature = "metrics")]
pub enable_metrics: bool,
// Other options not documented in Unified API
/// Disable SSL verification.
///
Expand Down Expand Up @@ -278,6 +281,9 @@ impl fmt::Debug for ClientOptions {
.field("enable_logs", &self.enable_logs)
.field("before_send_log", &before_send_log);

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

debug_struct.field("user_agent", &self.user_agent).finish()
}
}
Expand Down Expand Up @@ -317,6 +323,8 @@ impl Default for ClientOptions {
enable_logs: true,
#[cfg(feature = "logs")]
before_send_log: None,
#[cfg(feature = "metrics")]
enable_metrics: false,
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions sentry-core/src/hub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

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

#[cfg(feature = "metrics")]
use crate::protocol::Metric;
use crate::protocol::{Event, Level, Log, LogAttribute, LogLevel, Map, SessionStatus};
use crate::types::Uuid;
use crate::{Integration, IntoBreadcrumbs, Scope, ScopeGuard};
Expand Down Expand Up @@ -255,4 +257,14 @@ impl Hub {
client.capture_log(log, &top.scope);
}}
}

/// Captures a metric.
#[cfg(feature = "metrics")]
pub fn capture_metric(&self, metric: Metric) {
with_client_impl! {{
let top = self.inner.with(|stack| stack.top().clone());
let Some(ref client) = top.client else { return };
client.capture_metric(metric, &top.scope);
}}
}
}
2 changes: 1 addition & 1 deletion sentry-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ pub use crate::transport::{Transport, TransportFactory};
mod logger; // structured logging macros exported with `#[macro_export]`

// client feature
#[cfg(all(feature = "client", feature = "logs"))]
#[cfg(all(feature = "client", any(feature = "logs", feature = "metrics")))]
mod batcher;
#[cfg(feature = "client")]
mod client;
Expand Down
Loading
Loading