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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
### New Features

- Added a `Envelope::into_items` method, which returns an iterator over owned [`EnvelopeItem`s](https://docs.rs/sentry/0.46.2/sentry/protocol/enum.EnvelopeItem.html) in the [`Envelope`](https://docs.rs/sentry/0.46.2/sentry/struct.Envelope.html) ([#983](https://github.com/getsentry/sentry-rust/pull/983)).
- Add SDK protocol support for sending trace metric envelope items ([#1022](https://github.com/getsentry/sentry-rust/pull/1022)).
- Add `TraceMetric` and `TraceMetricType` types representing [trace metrics](https://develop.sentry.dev/sdk/telemetry/metrics/) ([#1026](https://github.com/getsentry/sentry-rust/pull/1026)).
- Add trace 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)).
- Expose transport utilities ([#949](https://github.com/getsentry/sentry-rust/pull/949))

### Fixes
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::TraceMetric;

// 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 TraceMetric {
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
107 changes: 105 additions & 2 deletions sentry-core/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::any::TypeId;
use std::borrow::Cow;
#[cfg(feature = "logs")]
#[cfg(any(feature = "logs", feature = "metrics"))]
use std::collections::BTreeMap;
use std::fmt;
use std::panic::RefUnwindSafe;
Expand All @@ -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,10 @@ use crate::SessionMode;
use crate::{ClientOptions, Envelope, Hub, Integration, Scope, Transport};
#[cfg(feature = "logs")]
use sentry_types::protocol::v7::Context;
#[cfg(all(feature = "metrics", not(feature = "logs")))]
use sentry_types::protocol::v7::LogAttribute;
#[cfg(feature = "metrics")]
use sentry_types::protocol::v7::TraceMetric;
#[cfg(feature = "logs")]
use sentry_types::protocol::v7::{Log, LogAttribute};

Expand Down Expand Up @@ -59,8 +63,12 @@ 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<TraceMetric>>>,
#[cfg(feature = "logs")]
default_log_attributes: Option<BTreeMap<String, LogAttribute>>,
#[cfg(feature = "metrics")]
default_metric_attributes: Option<BTreeMap<String, LogAttribute>>,
integrations: Vec<(TypeId, Arc<dyn Integration>)>,
pub(crate) sdk_info: ClientSdkInfo,
}
Expand Down Expand Up @@ -91,15 +99,26 @@ 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(),
#[cfg(feature = "metrics")]
default_metric_attributes: self.default_metric_attributes.clone(),
integrations: self.integrations.clone(),
sdk_info: self.sdk_info.clone(),
}
Expand Down Expand Up @@ -176,6 +195,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,15 +210,22 @@ impl Client {
session_flusher,
#[cfg(feature = "logs")]
logs_batcher,
#[cfg(feature = "metrics")]
metrics_batcher,
#[cfg(feature = "logs")]
default_log_attributes: None,
#[cfg(feature = "metrics")]
default_metric_attributes: None,
integrations,
sdk_info,
};

#[cfg(feature = "logs")]
client.cache_default_log_attributes();

#[cfg(feature = "metrics")]
client.cache_default_metric_attributes();

client
}

Expand Down Expand Up @@ -247,6 +280,35 @@ impl Client {
self.default_log_attributes = Some(attributes);
}

#[cfg(feature = "metrics")]
fn cache_default_metric_attributes(&mut self) {
let mut attributes = BTreeMap::new();

if let Some(environment) = self.options.environment.as_ref() {
attributes.insert("sentry.environment".to_owned(), environment.clone().into());
}

if let Some(release) = self.options.release.as_ref() {
attributes.insert("sentry.release".to_owned(), release.clone().into());
}

attributes.insert(
"sentry.sdk.name".to_owned(),
self.sdk_info.name.to_owned().into(),
);

attributes.insert(
"sentry.sdk.version".to_owned(),
self.sdk_info.version.to_owned().into(),
);

if let Some(server) = &self.options.server_name {
attributes.insert("server.address".to_owned(), server.clone().into());
}

self.default_metric_attributes = Some(attributes);
}

pub(crate) fn get_integration<I>(&self) -> Option<&I>
where
I: Integration,
Expand Down Expand Up @@ -420,6 +482,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 +505,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 +561,41 @@ impl Client {

Some(log)
}

/// Captures a trace metric and sends it to Sentry.
#[cfg(feature = "metrics")]
pub fn capture_metric(&self, metric: TraceMetric, scope: &Scope) {
if !self.options.enable_metrics {
return;
}
if let Some(metric) = self.prepare_metric(metric, scope) {
if let Some(ref batcher) = *self.metrics_batcher.read().unwrap() {
batcher.enqueue(metric);
}
}
}

/// 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: TraceMetric, scope: &Scope) -> Option<TraceMetric> {
scope.apply_to_metric(&mut metric, self.options.send_default_pii);

if let Some(default_attributes) = self.default_metric_attributes.as_ref() {
for (key, val) in default_attributes.iter() {
metric
.attributes
.entry(key.to_owned())
.or_insert(val.clone());
}
}

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

Some(metric)
}
}

// Make this unwind safe. It's not out of the box because of the
Expand Down
24 changes: 24 additions & 0 deletions 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::TraceMetric;
use crate::protocol::{Breadcrumb, Event};
use crate::types::Dsn;
use crate::{Integration, IntoDsn, TransportFactory};
Expand Down Expand Up @@ -172,6 +174,12 @@ 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 trace metrics should be sent to Sentry (defaults to true).
#[cfg(feature = "metrics")]
pub enable_metrics: bool,
/// Callback that is executed for each TraceMetric before sending.
#[cfg(feature = "metrics")]
pub before_send_metric: Option<BeforeCallback<TraceMetric>>,
// Other options not documented in Unified API
/// Disable SSL verification.
///
Expand Down Expand Up @@ -278,6 +286,18 @@ impl fmt::Debug for ClientOptions {
.field("enable_logs", &self.enable_logs)
.field("before_send_log", &before_send_log);

#[cfg(feature = "metrics")]
{
let before_send_metric = {
#[derive(Debug)]
struct BeforeSendMetric;
self.before_send_metric.as_ref().map(|_| BeforeSendMetric)
};
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 @@ -317,6 +337,10 @@ impl Default for ClientOptions {
enable_logs: true,
#[cfg(feature = "logs")]
before_send_log: None,
#[cfg(feature = "metrics")]
enable_metrics: false,
#[cfg(feature = "metrics")]
before_send_metric: None,
}
}
}
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::TraceMetric;
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 trace metric.
#[cfg(feature = "metrics")]
pub fn capture_metric(&self, metric: TraceMetric) {
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
7 changes: 7 additions & 0 deletions sentry-core/src/performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,13 @@ impl TransactionOrSpan {
TransactionOrSpan::Span(span) => span.finish(),
}
}

pub(crate) fn span_id(&self) -> SpanId {
match self {
TransactionOrSpan::Transaction(transaction) => transaction.get_trace_context().span_id,
TransactionOrSpan::Span(span) => span.get_span_id(),
}
}
}

#[derive(Debug)]
Expand Down
10 changes: 10 additions & 0 deletions sentry-core/src/scope/noop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::fmt;

#[cfg(feature = "logs")]
use crate::protocol::Log;
#[cfg(feature = "metrics")]
use crate::protocol::TraceMetric;
use crate::protocol::{Context, Event, Level, User, Value};
use crate::TransactionOrSpan;

Expand Down Expand Up @@ -119,6 +121,14 @@ impl Scope {
minimal_unreachable!();
}

/// Applies the contained scoped data to fill a trace metric.
#[cfg(feature = "metrics")]
pub fn apply_to_metric(&self, metric: &mut TraceMetric, send_default_pii: bool) {
let _metric = metric;
let _send_default_pii = send_default_pii;
minimal_unreachable!();
}

/// Set the given [`TransactionOrSpan`] as the active span for this scope.
pub fn set_span(&mut self, span: Option<TransactionOrSpan>) {
let _ = span;
Expand Down
Loading
Loading