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 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 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 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
112pub 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}