use crate::{sys, RELEASE_NAME};
use sentry_tracing::EventFilter;
use std::borrow::Cow;
const TRACE_TARGET: &str = "studio_worker::telemetry";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SentryConfig {
pub dsn: String,
pub environment: String,
pub release: String,
pub server_name: String,
}
impl SentryConfig {
pub fn from_env() -> Option<Self> {
Self::from_env_inner(
std::env::var("SENTRY_DSN").ok(),
std::env::var("SENTRY_ENVIRONMENT").ok(),
RELEASE_NAME.to_string(),
sys::machine_name(),
)
}
pub fn from_env_inner(
dsn: Option<String>,
environment: Option<String>,
release: String,
server_name: String,
) -> Option<Self> {
let dsn = dsn?.trim().to_string();
if dsn.is_empty() {
return None;
}
let environment = environment
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "production".to_string());
Some(Self {
dsn,
environment,
release,
server_name,
})
}
}
pub fn build_client_options(cfg: &SentryConfig) -> Option<sentry::ClientOptions> {
let dsn = match cfg.dsn.parse() {
Ok(parsed) => parsed,
Err(e) => {
tracing::warn!(
target: TRACE_TARGET,
error = %e,
"ignoring SENTRY_DSN: not a valid sentry DSN"
);
return None;
}
};
Some(sentry::ClientOptions {
dsn: Some(dsn),
release: Some(Cow::Owned(cfg.release.clone())),
environment: Some(Cow::Owned(cfg.environment.clone())),
server_name: Some(Cow::Owned(cfg.server_name.clone())),
traces_sample_rate: 0.0,
..Default::default()
})
}
pub fn init() -> Option<sentry::ClientInitGuard> {
let cfg = SentryConfig::from_env()?;
let options = build_client_options(&cfg)?;
let guard = sentry::init(options);
if !guard.is_enabled() {
tracing::warn!(
target: TRACE_TARGET,
"sentry::init returned a disabled client (likely invalid DSN); telemetry off"
);
return None;
}
tracing::info!(
target: TRACE_TARGET,
environment = %cfg.environment,
release = %cfg.release,
server_name = %cfg.server_name,
"sentry telemetry enabled"
);
Some(guard)
}
pub fn tracing_layer<S>() -> sentry_tracing::SentryLayer<S>
where
S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
{
sentry_tracing::layer().event_filter(|md| match *md.level() {
tracing::Level::ERROR => EventFilter::Event,
tracing::Level::WARN => EventFilter::Breadcrumb,
_ => EventFilter::Ignore,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::capture;
fn sample_config(dsn: &str) -> SentryConfig {
SentryConfig {
dsn: dsn.to_string(),
environment: "staging".into(),
release: "studio-worker@9.9.9".into(),
server_name: "rig-01".into(),
}
}
#[test]
fn build_client_options_carries_release_environment_and_server_name() {
let cfg = sample_config("https://abc123@o1.ingest.sentry.io/42");
let opts = build_client_options(&cfg).expect("a valid DSN must yield options");
assert!(opts.dsn.is_some(), "the parsed DSN must be attached");
assert_eq!(opts.release.as_deref(), Some("studio-worker@9.9.9"));
assert_eq!(opts.environment.as_deref(), Some("staging"));
assert_eq!(opts.server_name.as_deref(), Some("rig-01"));
assert!(
opts.traces_sample_rate.abs() < f32::EPSILON,
"traces_sample_rate must be disabled (0.0), got {}",
opts.traces_sample_rate
);
}
#[test]
fn build_client_options_rejects_invalid_dsn_and_warns() {
let cfg = sample_config("not-a-valid-dsn");
let logs = capture(move || {
assert!(
build_client_options(&cfg).is_none(),
"an unparseable DSN must yield no options"
);
});
assert!(logs.contains("WARN"), "expected WARN event, got: {logs}");
assert!(
logs.contains("studio_worker::telemetry"),
"expected telemetry target, got: {logs}"
);
assert!(
logs.contains("not a valid sentry DSN"),
"expected the invalid-DSN message, got: {logs}"
);
}
#[test]
fn from_env_inner_rejects_empty_string_after_trim() {
let resolved =
SentryConfig::from_env_inner(Some("\t \n".into()), None, "0.0.0".into(), "h".into());
assert!(resolved.is_none());
}
#[test]
fn from_env_inner_populates_all_fields() {
let cfg = SentryConfig::from_env_inner(
Some("https://k@example.ingest.sentry.io/1".into()),
Some("prod".into()),
"9.9.9".into(),
"machine".into(),
)
.expect("dsn set");
assert_eq!(cfg.dsn, "https://k@example.ingest.sentry.io/1");
assert_eq!(cfg.environment, "prod");
assert_eq!(cfg.release, "9.9.9");
assert_eq!(cfg.server_name, "machine");
}
}