diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 92eacbd5f..85c5a89f1 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -109,17 +109,18 @@ async fn try_main() -> anyhow::Result { // Load the base configuration files let figment = opts.figment(); - // Telemetry config could fail to load, but that's probably OK, since the whole - // config will be loaded afterwards, and crash if there is a problem. - // Falling back to default. - let telemetry_config = TelemetryConfig::extract(&figment).unwrap_or_default(); + let telemetry_config = + TelemetryConfig::extract(&figment).context("Failed to load telemetry config")?; // Setup Sentry let sentry = sentry::init(( telemetry_config.sentry.dsn.as_deref(), sentry::ClientOptions { transport: Some(Arc::new(SentryTransportFactory::new())), - traces_sample_rate: 1.0, + environment: telemetry_config.sentry.environment.clone().map(Into::into), + release: Some(VERSION.into()), + sample_rate: telemetry_config.sentry.sample_rate.unwrap_or(1.0), + traces_sample_rate: telemetry_config.sentry.traces_sample_rate.unwrap_or(0.0), auto_session_tracking: true, session_mode: sentry::SessionMode::Request, ..Default::default() diff --git a/crates/cli/src/telemetry.rs b/crates/cli/src/telemetry.rs index 79704d2e2..9fc67e43a 100644 --- a/crates/cli/src/telemetry.rs +++ b/crates/cli/src/telemetry.rs @@ -102,7 +102,10 @@ fn stdout_tracer_provider() -> SdkTracerProvider { .build() } -fn otlp_tracer_provider(endpoint: Option<&Url>) -> anyhow::Result { +fn otlp_tracer_provider( + endpoint: Option<&Url>, + sample_rate: f64, +) -> anyhow::Result { let mut exporter = opentelemetry_otlp::SpanExporter::builder() .with_http() .with_http_client(mas_http::reqwest_client()); @@ -119,17 +122,18 @@ fn otlp_tracer_provider(endpoint: Option<&Url>) -> anyhow::Result anyhow::Result<()> { + let sample_rate = config.sample_rate.unwrap_or(1.0); let tracer_provider = match config.exporter { TracingExporterKind::None => return Ok(()), TracingExporterKind::Stdout => stdout_tracer_provider(), - TracingExporterKind::Otlp => otlp_tracer_provider(config.endpoint.as_ref())?, + TracingExporterKind::Otlp => otlp_tracer_provider(config.endpoint.as_ref(), sample_rate)?, }; TRACER_PROVIDER .set(tracer_provider.clone()) diff --git a/crates/config/src/sections/telemetry.rs b/crates/config/src/sections/telemetry.rs index f4d73c29d..0c11e0285 100644 --- a/crates/config/src/sections/telemetry.rs +++ b/crates/config/src/sections/telemetry.rs @@ -5,12 +5,16 @@ // Please see LICENSE in the repository root for full details. use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::Error as _}; use serde_with::skip_serializing_none; use url::Url; use super::ConfigurationSection; +fn sample_rate_example() -> f64 { + 0.5 +} + /// Propagation format for incoming and outgoing requests #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "lowercase")] @@ -61,6 +65,13 @@ pub struct TracingConfig { /// List of propagation formats to use for incoming and outgoing requests #[serde(default)] pub propagators: Vec, + + /// Sample rate for traces + /// + /// Defaults to `1.0` if not set. + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(example = "sample_rate_example", range(min = 0.0, max = 1.0))] + pub sample_rate: Option, } impl TracingConfig { @@ -116,6 +127,10 @@ fn sentry_dsn_example() -> &'static str { "https://public@host:port/1" } +fn sentry_environment_example() -> &'static str { + "production" +} + /// Configuration related to the Sentry integration #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct SentryConfig { @@ -123,6 +138,27 @@ pub struct SentryConfig { #[schemars(url, example = "sentry_dsn_example")] #[serde(skip_serializing_if = "Option::is_none")] pub dsn: Option, + + /// Environment to use when sending events to Sentry + /// + /// Defaults to `production` if not set. + #[schemars(example = "sentry_environment_example")] + #[serde(skip_serializing_if = "Option::is_none")] + pub environment: Option, + + /// Sample rate for event submissions + /// + /// Defaults to `1.0` if not set. + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(example = "sample_rate_example", range(min = 0.0, max = 1.0))] + pub sample_rate: Option, + + /// Sample rate for tracing transactions + /// + /// Defaults to `0.0` if not set. + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(example = "sample_rate_example", range(min = 0.0, max = 1.0))] + pub traces_sample_rate: Option, } impl SentryConfig { @@ -157,4 +193,35 @@ impl TelemetryConfig { impl ConfigurationSection for TelemetryConfig { const PATH: Option<&'static str> = Some("telemetry"); + + fn validate(&self, _figment: &figment::Figment) -> Result<(), figment::Error> { + if let Some(sample_rate) = self.sentry.sample_rate { + if !(0.0..=1.0).contains(&sample_rate) { + return Err(figment::error::Error::custom( + "Sentry sample rate must be between 0.0 and 1.0", + ) + .with_path("sentry.sample_rate")); + } + } + + if let Some(sample_rate) = self.sentry.traces_sample_rate { + if !(0.0..=1.0).contains(&sample_rate) { + return Err(figment::error::Error::custom( + "Sentry sample rate must be between 0.0 and 1.0", + ) + .with_path("sentry.traces_sample_rate")); + } + } + + if let Some(sample_rate) = self.tracing.sample_rate { + if !(0.0..=1.0).contains(&sample_rate) { + return Err(figment::error::Error::custom( + "Tracing sample rate must be between 0.0 and 1.0", + ) + .with_path("tracing.sample_rate")); + } + } + + Ok(()) + } } diff --git a/docs/config.schema.json b/docs/config.schema.json index 0d8325529..2a4a2c719 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -1213,6 +1213,16 @@ "items": { "$ref": "#/definitions/Propagator" } + }, + "sample_rate": { + "description": "Sample rate for traces\n\nDefaults to `1.0` if not set.", + "examples": [ + 0.5 + ], + "type": "number", + "format": "double", + "maximum": 1.0, + "minimum": 0.0 } } }, @@ -1333,6 +1343,33 @@ ], "type": "string", "format": "uri" + }, + "environment": { + "description": "Environment to use when sending events to Sentry\n\nDefaults to `production` if not set.", + "examples": [ + "production" + ], + "type": "string" + }, + "sample_rate": { + "description": "Sample rate for event submissions\n\nDefaults to `1.0` if not set.", + "examples": [ + 0.5 + ], + "type": "number", + "format": "float", + "maximum": 1.0, + "minimum": 0.0 + }, + "traces_sample_rate": { + "description": "Sample rate for tracing transactions\n\nDefaults to `0.0` if not set.", + "examples": [ + 0.5 + ], + "type": "number", + "format": "float", + "maximum": 1.0, + "minimum": 0.0 } } },