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` is ignored.
19pub fn init(config: &LoggingConfig) {
20    #[cfg(feature = "sentry")]
21    let sentry_layer = sentry::Hub::current()
22        .client()
23        .filter(|c| c.is_enabled())
24        .map(|_| {
25            use sentry::integrations::tracing as sentry_tracing;
26            sentry_tracing::layer().event_filter(|metadata| match *metadata.level() {
27                Level::ERROR | Level::WARN => {
28                    sentry_tracing::EventFilter::Event | sentry_tracing::EventFilter::Log
29                }
30                Level::INFO | Level::DEBUG => sentry_tracing::EventFilter::Log,
31                Level::TRACE => sentry_tracing::EventFilter::Ignore,
32            })
33        });
34
35    let format = tracing_subscriber::fmt::layer()
36        .with_writer(std::io::stderr)
37        .with_target(true);
38
39    let format: Box<dyn tracing_subscriber::Layer<Registry> + Send + Sync> =
40        match (config.format, console::user_attended()) {
41            (LogFormat::Auto, true) | (LogFormat::Pretty, _) => {
42                format.compact().without_time().boxed()
43            }
44            (LogFormat::Auto, false) | (LogFormat::Simplified, _) => {
45                format.with_ansi(false).boxed()
46            }
47            (LogFormat::Json, _) => format
48                .json()
49                .flatten_event(true)
50                .with_current_span(true)
51                .with_span_list(true)
52                .with_file(true)
53                .with_line_number(true)
54                .boxed(),
55        };
56
57    let env_filter = match EnvFilter::try_from_default_env() {
58        Ok(filter) => filter,
59        Err(_) => default_filter(),
60    };
61
62    #[cfg(not(feature = "sentry"))]
63    tracing_subscriber::registry()
64        .with(format.with_filter(config.level))
65        .with(env_filter)
66        .init();
67
68    #[cfg(feature = "sentry")]
69    tracing_subscriber::registry()
70        .with(format.with_filter(config.level))
71        .with(sentry_layer)
72        .with(env_filter)
73        .init();
74}
75
76/// Builds the default [`EnvFilter`] when `RUST_LOG` is not set.
77///
78/// Uses `INFO` as the base level with `DEBUG` for `tower_http`, then sets `TRACE` for every
79/// internal objectstore crate discovered at build time.
80fn default_filter() -> EnvFilter {
81    let mut filter = EnvFilter::new("INFO,tower_http=DEBUG");
82
83    for name in CRATE_NAMES {
84        // INVARIANT: crate names are valid identifiers; the directive cannot fail to parse.
85        filter = filter.add_directive(format!("{name}=TRACE").parse().unwrap());
86    }
87
88    filter
89}