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    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    // Same as the default filter, except it converts warnings into events
55    // and also sends everything at or above INFO as logs instead of breadcrumbs.
56    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        // INFO by default. Use stricter levels for noisy crates. Use looser levels
86        // for internal crates and essential dependencies.
87        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
104/// Logs an error to the configured logger or `stderr` if not yet configured.
105pub 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}