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
81 changes: 71 additions & 10 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 @@ -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(all(feature = "metrics", not(feature = "logs")))]
use sentry_types::protocol::v7::LogAttribute;
#[cfg(feature = "metrics")]
use sentry_types::protocol::v7::Metric;
#[cfg(feature = "logs")]
Expand Down Expand Up @@ -65,6 +67,8 @@ pub struct Client {
metrics_batcher: RwLock<Option<Batcher<Metric>>>,
#[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 @@ -113,6 +117,8 @@ impl Clone for Client {
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 @@ -208,13 +214,18 @@ impl Client {
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 @@ -269,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 @@ -524,16 +564,37 @@ impl Client {

/// 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);
pub fn capture_metric(&self, metric: Metric, 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: Metric, scope: &Scope) -> Option<Metric> {
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)
}
}

Expand Down
18 changes: 17 additions & 1 deletion 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::Metric;
use crate::protocol::{Breadcrumb, Event};
use crate::types::Dsn;
use crate::{Integration, IntoDsn, TransportFactory};
Expand Down Expand Up @@ -175,6 +177,9 @@ pub struct ClientOptions {
/// Determines whether captured metrics should be sent to Sentry (defaults to true).
#[cfg(feature = "metrics")]
pub enable_metrics: bool,
/// Callback that is executed for each Metric before sending.
#[cfg(feature = "metrics")]
pub before_send_metric: Option<BeforeCallback<Metric>>,
// Other options not documented in Unified API
/// Disable SSL verification.
///
Expand Down Expand Up @@ -282,7 +287,16 @@ impl fmt::Debug for ClientOptions {
.field("before_send_log", &before_send_log);

#[cfg(feature = "metrics")]
debug_struct.field("enable_metrics", &self.enable_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 @@ -325,6 +339,8 @@ impl Default for ClientOptions {
before_send_log: None,
#[cfg(feature = "metrics")]
enable_metrics: false,
#[cfg(feature = "metrics")]
before_send_metric: None,
}
}
}
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::Metric;
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 Metric, 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
54 changes: 52 additions & 2 deletions sentry-core/src/scope/real.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ use std::sync::Mutex;
use std::sync::{Arc, PoisonError, RwLock};

use crate::performance::TransactionOrSpan;
#[cfg(feature = "logs")]
use crate::protocol::Log;
#[cfg(any(feature = "logs", feature = "metrics"))]
use crate::protocol::LogAttribute;
#[cfg(feature = "metrics")]
use crate::protocol::Metric;
use crate::protocol::{
Attachment, Breadcrumb, Context, Event, Level, TraceContext, Transaction, User, Value,
};
#[cfg(feature = "logs")]
use crate::protocol::{Log, LogAttribute};
#[cfg(feature = "release-health")]
use crate::session::Session;
use crate::{Client, SentryTrace, TraceHeader, TraceHeadersIter};
Expand Down Expand Up @@ -399,6 +403,33 @@ impl Scope {
}
}

/// Applies the contained scoped data to a trace metric, setting the `trace_id`, `span_id`,
/// and certain default attributes. User PII attributes are only attached when
/// `send_default_pii` is `true`.
#[cfg(feature = "metrics")]
pub fn apply_to_metric(&self, metric: &mut Metric, send_default_pii: bool) {
metric.trace_id = match self.span.as_ref().as_ref() {
Some(span) => span.get_trace_context().trace_id,
None => self.propagation_context.trace_id,
};

metric.span_id = metric
.span_id
.or_else(|| self.get_span().map(|ts| ts.span_id()));

if !send_default_pii {
return;
}

let Some(user) = self.user.as_ref() else {
return;
};

metric.insert_attribute("user.id", user.id.as_deref());
metric.insert_attribute("user.name", user.username.as_deref());
metric.insert_attribute("user.email", user.email.as_deref());
}

/// Set the given [`TransactionOrSpan`] as the active span for this scope.
pub fn set_span(&mut self, span: Option<TransactionOrSpan>) {
self.span = Arc::new(span);
Expand Down Expand Up @@ -444,3 +475,22 @@ impl Scope {
}
}
}

#[cfg(feature = "metrics")]
trait MetricExt {
fn insert_attribute<K, V>(&mut self, key: K, value: V)
where
K: Into<String>,
V: Into<Value>;
}

#[cfg(feature = "metrics")]
impl MetricExt for Metric {
fn insert_attribute<K, V>(&mut self, key: K, value: V)
where
K: Into<String>,
V: Into<Value>,
{
self.attributes.insert(key, LogAttribute(value.into()));
}
}
Loading
Loading