objectstore_server/
observability.rs

1use 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
9/// The full release name including the objectstore version and SHA.
10const 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    let guard = 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    sentry::configure_scope(|scope| {
53        for (k, v) in &config.tags {
54            scope.set_tag(k, v);
55        }
56    });
57
58    Some(guard)
59}
60
61pub fn init_tracing(config: &Config) {
62    // Same as the default filter, except it converts warnings into events
63    // and also sends everything at or above INFO as logs instead of breadcrumbs.
64    let sentry_layer = config.sentry.is_enabled().then(|| {
65        sentry_tracing::layer().event_filter(|metadata| match *metadata.level() {
66            Level::ERROR | Level::WARN => {
67                sentry_tracing::EventFilter::Event | sentry_tracing::EventFilter::Log
68            }
69            Level::INFO | Level::DEBUG => sentry_tracing::EventFilter::Log,
70            Level::TRACE => sentry_tracing::EventFilter::Ignore,
71        })
72    });
73
74    let format = tracing_subscriber::fmt::layer()
75        .with_writer(std::io::stderr)
76        .with_target(true);
77
78    let format = match (config.logging.format, console::user_attended()) {
79        (LogFormat::Auto, true) | (LogFormat::Pretty, _) => format.compact().without_time().boxed(),
80        (LogFormat::Auto, false) | (LogFormat::Simplified, _) => format.with_ansi(false).boxed(),
81        (LogFormat::Json, _) => format
82            .json()
83            .flatten_event(true)
84            .with_current_span(true)
85            .with_span_list(true)
86            .with_file(true)
87            .with_line_number(true)
88            .boxed(),
89    };
90
91    let env_filter = match EnvFilter::try_from_default_env() {
92        Ok(env_filter) => env_filter,
93        // INFO by default. Use stricter levels for noisy crates. Use looser levels
94        // for internal crates and essential dependencies.
95        Err(_) => EnvFilter::new(
96            "INFO,\
97            tower_http=DEBUG,\
98            objectstore=TRACE,\
99            objectstore_service=TRACE,\
100            objectstore_types=TRACE,\
101            ",
102        ),
103    };
104
105    tracing_subscriber::registry()
106        .with(format.with_filter(config.logging.level))
107        .with(sentry_layer)
108        .with(env_filter)
109        .init();
110}
111
112/// Logs an error to the configured logger or `stderr` if not yet configured.
113pub fn ensure_log_error(error: &anyhow::Error) {
114    if tracing::Level::ERROR <= tracing::level_filters::STATIC_MAX_LEVEL
115        && tracing::Level::ERROR <= LevelFilter::current()
116    {
117        tracing::error!(error = error.as_ref() as &dyn std::error::Error);
118    } else {
119        eprintln!("{error:?}");
120    }
121}