spring_boot/log/
mod.rs

1mod config;
2
3use crate::{app::AppBuilder, config::Configurable};
4use config::{Format, LogLevel, LoggerConfig};
5use std::sync::OnceLock;
6use tracing_appender::non_blocking::WorkerGuard;
7use tracing_subscriber::filter::EnvFilter;
8use tracing_subscriber::layer::SubscriberExt;
9use tracing_subscriber::util::SubscriberInitExt;
10use tracing_subscriber::{
11    fmt::{self, MakeWriter},
12    Layer, Registry,
13};
14
15pub(crate) struct LogPlugin;
16
17impl Configurable for LogPlugin {
18    fn config_prefix(&self) -> &str {
19        "logger"
20    }
21}
22
23impl LogPlugin {
24    pub(crate) fn build(&self, app: &mut AppBuilder) {
25        let registry = std::mem::take(&mut app.tracing_registry);
26
27        let config = app
28            .get_config::<LoggerConfig>(self)
29            .expect("tracing plugin config load failed");
30
31        if config.pretty_backtrace {
32            std::env::set_var("RUST_BACKTRACE", "1");
33            log::warn!(
34                "pretty backtraces are enabled (this is great for development but has a runtime cost for production. disable with `logger.pretty_backtrace` in your config)"
35            );
36        }
37
38        let layers = build_logger_layers(&config);
39
40        if !layers.is_empty() {
41            let env_filter = init_env_filter(config.override_filter, &config.level);
42            registry.with(layers).with(env_filter).init();
43        }
44    }
45}
46
47// Keep nonblocking file appender work guard
48static NONBLOCKING_WORK_GUARD_KEEP: OnceLock<WorkerGuard> = OnceLock::new();
49
50fn build_logger_layers(config: &LoggerConfig) -> Vec<Box<dyn Layer<Registry> + Sync + Send>> {
51    let mut layers = Vec::new();
52
53    if let Some(file_config) = &config.file_appender {
54        if file_config.enable {
55            let file_appender = tracing_appender::rolling::Builder::default()
56                .max_log_files(file_config.max_log_files)
57                .filename_prefix(&file_config.filename_prefix)
58                .filename_suffix(&file_config.filename_suffix)
59                .rotation(file_config.rotation.clone().into())
60                .build(&file_config.dir)
61                .expect("logger file appender initialization failed");
62
63            let file_appender_layer = if file_config.non_blocking {
64                let (non_blocking_file_appender, work_guard) =
65                    tracing_appender::non_blocking(file_appender);
66                NONBLOCKING_WORK_GUARD_KEEP.set(work_guard).unwrap();
67                build_fmt_layer(non_blocking_file_appender, &file_config.format, false)
68            } else {
69                build_fmt_layer(file_appender, &file_config.format, false)
70            };
71            layers.push(file_appender_layer);
72        }
73    }
74
75    if config.enable {
76        layers.push(build_fmt_layer(std::io::stdout, &config.format, true));
77    }
78
79    layers
80}
81
82fn build_fmt_layer<W2>(
83    make_writer: W2,
84    format: &Format,
85    ansi: bool,
86) -> Box<dyn Layer<Registry> + Sync + Send>
87where
88    W2: for<'writer> MakeWriter<'writer> + Sync + Send + 'static,
89{
90    match format {
91        Format::Compact => fmt::Layer::default()
92            .with_ansi(ansi)
93            .with_writer(make_writer)
94            .compact()
95            .boxed(),
96        Format::Pretty => fmt::Layer::default()
97            .with_ansi(ansi)
98            .with_writer(make_writer)
99            .pretty()
100            .boxed(),
101        Format::Json => fmt::Layer::default()
102            .with_ansi(ansi)
103            .with_writer(make_writer)
104            .json()
105            .boxed(),
106    }
107}
108
109fn init_env_filter(override_filter: Option<String>, level: &LogLevel) -> EnvFilter {
110    EnvFilter::try_from_default_env()
111        .or_else(|_| {
112            // user wanted a specific filter, don't care about our internal whitelist
113            // or, if no override give them the default whitelisted filter (most common)
114            EnvFilter::try_new(override_filter.unwrap_or(format!("{level}")))
115        })
116        .expect("logger initialization failed")
117}