Skip to main content

objectstore_log/
subscriber.rs

1use tracing_subscriber::prelude::*;
2use tracing_subscriber::{EnvFilter, Registry};
3
4use crate::Level;
5use crate::config::{LogFormat, LoggingConfig};
6
7// Generated by build.rs: `const CRATE_NAMES: &[&str]` listing all objectstore workspace crates.
8include!(concat!(env!("OUT_DIR"), "/constants.gen.rs"));
9
10/// Initializes the global tracing subscriber with structured logging.
11///
12/// Reads `RUST_LOG` for filter directives; falls back to `INFO`-level logging with `TRACE`-level
13/// for internal objectstore crates. Log format (`pretty`, `simplified`, or `json`) is determined
14/// by `config.format`, defaulting to pretty when a terminal is attached.
15///
16/// When built with the `sentry` feature, also attaches a Sentry tracing layer if the Sentry client
17/// has been initialized. `ERROR` and `WARN` events become Sentry events; `INFO`/`DEBUG` become
18/// Sentry logs; `TRACE` events are ignored. `ERROR`..`DEBUG` spans become Sentry spans; `TRACE`
19/// spans are ignored.
20pub fn init(config: &LoggingConfig) {
21    #[cfg(feature = "sentry")]
22    let sentry_layer = sentry::Hub::current()
23        .client()
24        .filter(|c| c.is_enabled())
25        .map(|_| {
26            use sentry::integrations::tracing as sentry_tracing;
27            sentry_tracing::layer()
28                .event_filter(|metadata| match *metadata.level() {
29                    Level::ERROR | Level::WARN => {
30                        sentry_tracing::EventFilter::Event | sentry_tracing::EventFilter::Log
31                    }
32                    Level::INFO | Level::DEBUG => sentry_tracing::EventFilter::Log,
33                    Level::TRACE => sentry_tracing::EventFilter::Ignore,
34                })
35                .span_filter(|metadata| !matches!(*metadata.level(), Level::TRACE))
36        });
37
38    let format = tracing_subscriber::fmt::layer()
39        .with_writer(std::io::stderr)
40        .with_target(true);
41
42    let format: Box<dyn tracing_subscriber::Layer<Registry> + Send + Sync> =
43        match (config.format, console::user_attended()) {
44            (LogFormat::Auto, true) | (LogFormat::Pretty, _) => {
45                format.compact().without_time().boxed()
46            }
47            (LogFormat::Auto, false) | (LogFormat::Simplified, _) => {
48                format.with_ansi(false).boxed()
49            }
50            (LogFormat::Json, _) => format
51                .json()
52                .flatten_event(true)
53                .with_current_span(true)
54                .with_span_list(true)
55                .with_file(true)
56                .with_line_number(true)
57                .boxed(),
58        };
59
60    let env_filter = match EnvFilter::try_from_default_env() {
61        Ok(filter) => filter,
62        Err(_) => default_filter(),
63    };
64
65    #[cfg(not(feature = "sentry"))]
66    tracing_subscriber::registry()
67        .with(format.with_filter(config.level))
68        .with(env_filter)
69        .init();
70
71    #[cfg(feature = "sentry")]
72    tracing_subscriber::registry()
73        .with(format.with_filter(config.level))
74        .with(sentry_layer)
75        .with(env_filter)
76        .init();
77}
78
79/// Builds the default [`EnvFilter`] when `RUST_LOG` is not set.
80///
81/// Uses `INFO` as the base level with `DEBUG` for `tower_http`, then sets `TRACE` for every
82/// internal objectstore crate discovered at build time.
83fn default_filter() -> EnvFilter {
84    let mut filter = EnvFilter::new("INFO,tower_http=DEBUG");
85
86    for name in CRATE_NAMES {
87        // INVARIANT: crate names are valid identifiers; the directive cannot fail to parse.
88        filter = filter.add_directive(format!("{name}=TRACE").parse().unwrap());
89    }
90
91    filter
92}