1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::env;
4use std::fmt::{self, Display};
5use std::path::PathBuf;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use relay_common::impl_str_serde;
10use sentry::integrations::tracing::EventFilter;
11use sentry::types::Dsn;
12use sentry::{TracesSampler, TransactionContext};
13use serde::{Deserialize, Serialize};
14use tracing::level_filters::LevelFilter;
15use tracing_subscriber::{EnvFilter, Layer, prelude::*};
16
17use crate::crash;
18
19const RELEASE: &str = std::env!("RELAY_RELEASE");
21
22include!(concat!(env!("OUT_DIR"), "/constants.gen.rs"));
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
27#[serde(rename_all = "lowercase")]
28pub enum LogFormat {
29 Auto,
33
34 Pretty,
40
41 Simplified,
47
48 Json,
54}
55
56#[derive(Clone, Debug)]
58pub struct FormatParseError(String);
59
60impl Display for FormatParseError {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(
63 f,
64 r#"error parsing "{}" as format: expected one of "auto", "pretty", "simplified", "json""#,
65 self.0
66 )
67 }
68}
69
70impl FromStr for LogFormat {
71 type Err = FormatParseError;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 let result = match s {
75 "" => LogFormat::Auto,
76 s if s.eq_ignore_ascii_case("auto") => LogFormat::Auto,
77 s if s.eq_ignore_ascii_case("pretty") => LogFormat::Pretty,
78 s if s.eq_ignore_ascii_case("simplified") => LogFormat::Simplified,
79 s if s.eq_ignore_ascii_case("json") => LogFormat::Json,
80 s => return Err(FormatParseError(s.into())),
81 };
82
83 Ok(result)
84 }
85}
86
87impl std::error::Error for FormatParseError {}
88
89#[derive(Clone, Debug)]
91pub struct LevelParseError(String);
92
93impl Display for LevelParseError {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 write!(
96 f,
97 r#"error parsing "{}" as level: expected one of "error", "warn", "info", "debug", "trace", "off""#,
98 self.0
99 )
100 }
101}
102
103#[derive(Clone, Copy, Debug)]
104pub enum Level {
105 Error,
106 Warn,
107 Info,
108 Debug,
109 Trace,
110 Off,
111}
112
113impl_str_serde!(Level, "The logging level.");
114
115impl Level {
116 pub const fn level_filter(&self) -> LevelFilter {
118 match self {
119 Level::Error => LevelFilter::ERROR,
120 Level::Warn => LevelFilter::WARN,
121 Level::Info => LevelFilter::INFO,
122 Level::Debug => LevelFilter::DEBUG,
123 Level::Trace => LevelFilter::TRACE,
124 Level::Off => LevelFilter::OFF,
125 }
126 }
127}
128
129impl Display for Level {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 write!(f, "{}", format!("{self:?}").to_lowercase())
132 }
133}
134
135impl FromStr for Level {
136 type Err = LevelParseError;
137
138 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 let result = match s {
140 "" => Level::Error,
141 s if s.eq_ignore_ascii_case("error") => Level::Error,
142 s if s.eq_ignore_ascii_case("warn") => Level::Warn,
143 s if s.eq_ignore_ascii_case("info") => Level::Info,
144 s if s.eq_ignore_ascii_case("debug") => Level::Debug,
145 s if s.eq_ignore_ascii_case("trace") => Level::Trace,
146 s if s.eq_ignore_ascii_case("off") => Level::Off,
147 s => return Err(LevelParseError(s.into())),
148 };
149
150 Ok(result)
151 }
152}
153
154impl std::error::Error for LevelParseError {}
155
156#[derive(Clone, Debug, Deserialize, Serialize)]
158#[serde(default)]
159pub struct LogConfig {
160 pub level: Level,
162
163 pub format: LogFormat,
167
168 pub enable_backtraces: bool,
172
173 pub traces_sample_rate: f32,
177}
178
179impl LogConfig {
180 pub const fn level_filter(&self) -> LevelFilter {
182 self.level.level_filter()
183 }
184}
185
186impl Default for LogConfig {
187 fn default() -> Self {
188 Self {
189 level: Level::Info,
190 format: LogFormat::Auto,
191 enable_backtraces: false,
192 #[cfg(debug_assertions)]
193 traces_sample_rate: 1.0,
194 #[cfg(not(debug_assertions))]
195 traces_sample_rate: 0.0,
196 }
197 }
198}
199
200#[derive(Clone, Debug, Deserialize, Serialize)]
202#[serde(default)]
203pub struct SentryConfig {
204 pub dsn: Option<Dsn>,
206
207 pub enabled: bool,
209
210 pub environment: Option<Cow<'static, str>>,
212
213 pub server_name: Option<Cow<'static, str>>,
218
219 pub default_tags: Option<BTreeMap<String, String>>,
221
222 pub _crash_db: Option<PathBuf>,
225}
226
227impl SentryConfig {
228 pub fn enabled_dsn(&self) -> Option<&Dsn> {
230 self.dsn.as_ref().filter(|_| self.enabled)
231 }
232}
233
234impl Default for SentryConfig {
235 fn default() -> Self {
236 Self {
237 dsn: "https://0cc4a37e5aab4da58366266a87a95740@sentry.io/1269704"
238 .parse()
239 .ok(),
240 enabled: false,
241 environment: None,
242 server_name: None,
243 default_tags: None,
244 _crash_db: None,
245 }
246 }
247}
248
249fn get_default_filters() -> EnvFilter {
251 let mut env_filter = EnvFilter::new(
253 "INFO,\
254 sqlx=WARN,\
255 tower_http=TRACE,\
256 trust_dns_proto=WARN,\
257 minidump=ERROR,\
258 metrics_exporter_dogstatsd::forwarder::sync=OFF,\
259 ",
260 );
261
262 for name in CRATE_NAMES {
264 env_filter = env_filter.add_directive(format!("{name}=TRACE").parse().unwrap());
265 }
266
267 env_filter
268}
269
270pub unsafe fn init(config: &LogConfig, sentry: &SentryConfig) {
290 if config.enable_backtraces {
291 unsafe {
292 env::set_var("RUST_BACKTRACE", "full");
293 }
294 }
295
296 let subscriber = tracing_subscriber::fmt::layer()
297 .with_writer(std::io::stderr)
298 .with_target(true);
299
300 let format = match (config.format, console::user_attended()) {
301 (LogFormat::Auto, true) | (LogFormat::Pretty, _) => {
302 subscriber.compact().without_time().boxed()
303 }
304 (LogFormat::Auto, false) | (LogFormat::Simplified, _) => {
305 subscriber.with_ansi(false).boxed()
306 }
307 (LogFormat::Json, _) => subscriber
308 .json()
309 .flatten_event(true)
310 .with_current_span(true)
311 .with_span_list(true)
312 .with_file(true)
313 .with_line_number(true)
314 .boxed(),
315 };
316
317 tracing_subscriber::registry()
318 .with(format.with_filter(config.level_filter()))
319 .with(
320 sentry::integrations::tracing::layer().event_filter(|md| match *md.level() {
323 tracing::Level::ERROR | tracing::Level::WARN => {
324 EventFilter::Event | EventFilter::Log
325 }
326 tracing::Level::INFO => EventFilter::Log,
327 tracing::Level::DEBUG | tracing::Level::TRACE => EventFilter::Ignore,
328 }),
329 )
330 .with(match env::var(EnvFilter::DEFAULT_ENV) {
331 Ok(value) => EnvFilter::new(value),
332 Err(_) => get_default_filters(),
333 })
334 .init();
335
336 if let Some(dsn) = sentry.enabled_dsn() {
337 let traces_sample_rate = config.traces_sample_rate;
338 let traces_sampler =
346 Some(Arc::new(move |_: &TransactionContext| traces_sample_rate) as Arc<TracesSampler>);
347 let mut options = sentry::ClientOptions {
348 dsn: Some(dsn).cloned(),
349 in_app_include: vec!["relay"],
350 release: Some(RELEASE.into()),
351 attach_stacktrace: config.enable_backtraces,
352 environment: sentry.environment.clone(),
353 server_name: sentry.server_name.clone(),
354 traces_sampler,
355 enable_logs: true,
356 ..Default::default()
357 };
358
359 if let Some(default_tags) = sentry.default_tags.clone() {
362 options.before_send = Some(Arc::new(move |mut event| {
364 let previous_event_tags = std::mem::replace(&mut event.tags, default_tags.clone());
366 event.tags.extend(previous_event_tags);
367 Some(event)
368 }));
369 }
370
371 if !crash::is_crash_reporter_process() {
372 crate::info!(
373 release = RELEASE,
374 server_name = sentry.server_name.as_deref(),
375 environment = sentry.environment.as_deref(),
376 traces_sample_rate,
377 "Initialized Sentry client options"
378 );
379 }
380
381 let guard = sentry::init(options);
382
383 #[cfg(feature = "crash-handler")]
386 {
387 crate::crash::init(sentry, (*guard).clone());
388 }
389
390 std::mem::forget(guard);
392 }
393}