objectstore_server/
observability.rs1use secrecy::ExposeSecret;
2use sentry::integrations::tracing as sentry_tracing;
3use tracing::Level;
4use tracing::level_filters::LevelFilter;
5use tracing_subscriber::{EnvFilter, prelude::*};
6
7use crate::config::{Config, LogFormat};
8
9const RELEASE: &str = std::env!("OBJECTSTORE_RELEASE");
11
12pub fn init_metrics(config: &Config) -> std::io::Result<Option<merni::DatadogFlusher>> {
13 let Some(ref api_key) = config.metrics.datadog_key else {
14 return Ok(None);
15 };
16
17 let mut builder = merni::datadog(api_key.expose_secret().as_str()).prefix("objectstore.");
18 for (k, v) in &config.metrics.tags {
19 builder = builder.global_tag(k, v);
20 }
21 builder.try_init().map(Some)
22}
23
24pub fn init_sentry(config: &Config) -> Option<sentry::ClientInitGuard> {
25 let config = &config.sentry;
26 let dsn = config.dsn.as_ref()?;
27
28 Some(sentry::init(sentry::ClientOptions {
29 dsn: dsn.expose_secret().parse().ok(),
30 release: Some(RELEASE.into()),
31 environment: config.environment.clone(),
32 server_name: config.server_name.clone(),
33 sample_rate: config.sample_rate,
34 traces_sampler: {
35 let traces_sample_rate = config.traces_sample_rate;
36 let inherit_sampling_decision = config.inherit_sampling_decision;
37 Some(std::sync::Arc::new(move |ctx| {
38 if let Some(sampled) = ctx.sampled()
39 && inherit_sampling_decision
40 {
41 f32::from(sampled)
42 } else {
43 traces_sample_rate
44 }
45 }))
46 },
47 enable_logs: true,
48 debug: config.debug,
49 ..Default::default()
50 }))
51}
52
53pub fn init_tracing(config: &Config) {
54 let sentry_layer = config.sentry.is_enabled().then(|| {
57 sentry_tracing::layer().event_filter(|metadata| match *metadata.level() {
58 Level::ERROR | Level::WARN => {
59 sentry_tracing::EventFilter::Event | sentry_tracing::EventFilter::Log
60 }
61 Level::INFO | Level::DEBUG => sentry_tracing::EventFilter::Log,
62 Level::TRACE => sentry_tracing::EventFilter::Ignore,
63 })
64 });
65
66 let format = tracing_subscriber::fmt::layer()
67 .with_writer(std::io::stderr)
68 .with_target(true);
69
70 let format = match (config.logging.format, console::user_attended()) {
71 (LogFormat::Auto, true) | (LogFormat::Pretty, _) => format.compact().without_time().boxed(),
72 (LogFormat::Auto, false) | (LogFormat::Simplified, _) => format.with_ansi(false).boxed(),
73 (LogFormat::Json, _) => format
74 .json()
75 .flatten_event(true)
76 .with_current_span(true)
77 .with_span_list(true)
78 .with_file(true)
79 .with_line_number(true)
80 .boxed(),
81 };
82
83 let env_filter = match EnvFilter::try_from_default_env() {
84 Ok(env_filter) => env_filter,
85 Err(_) => EnvFilter::new(
88 "INFO,\
89 tower_http=DEBUG,\
90 objectstore=TRACE,\
91 objectstore_service=TRACE,\
92 objectstore_types=TRACE,\
93 ",
94 ),
95 };
96
97 tracing_subscriber::registry()
98 .with(format.with_filter(config.logging.level))
99 .with(sentry_layer)
100 .with(env_filter)
101 .init();
102}
103
104pub fn ensure_log_error(error: &anyhow::Error) {
106 if tracing::Level::ERROR <= tracing::level_filters::STATIC_MAX_LEVEL
107 && tracing::Level::ERROR <= LevelFilter::current()
108 {
109 tracing::error!(error = error.as_ref() as &dyn std::error::Error);
110 } else {
111 eprintln!("{error:?}");
112 }
113}