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
47static 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 EnvFilter::try_new(override_filter.unwrap_or(format!("{level}")))
115 })
116 .expect("logger initialization failed")
117}