1use crate::config::{LogConfig, LogFormat, RotationStrategy};
2use crate::detector::{LoggingMode, SubscriberDetector};
3use crate::error::Result;
4use parking_lot::RwLock;
5use std::path::Path;
6use std::sync::Arc;
7
8pub struct Logger {
10 config: LogConfig,
12 mode: Option<LoggingMode>,
14 initialized: Arc<RwLock<bool>>,
16 _file_guard: Option<tracing_appender::non_blocking::WorkerGuard>,
18}
19
20impl Logger {
21 pub fn new(config: LogConfig) -> Result<Self> {
23 let mode = SubscriberDetector::detect_mode();
24 Ok(Self {
25 config,
26 mode: Some(mode),
27 initialized: Arc::new(RwLock::new(false)),
28 _file_guard: None,
29 })
30 }
31
32 pub fn new_with_mode(config: LogConfig, mode: LoggingMode) -> Self {
34 Self {
35 config,
36 mode: Some(mode),
37 initialized: Arc::new(RwLock::new(false)),
38 _file_guard: None,
39 }
40 }
41
42 pub fn init(&mut self) -> Result<()> {
47 {
48 let initialized = self.initialized.read();
49 if *initialized {
50 return Ok(());
51 }
52 }
53
54 let mode = self
55 .mode
56 .unwrap_or_else(|| SubscriberDetector::detect_mode());
57
58 match mode {
59 LoggingMode::Library => {
60 tracing::debug!("使用库模式,不初始化 subscriber");
62 *self.initialized.write() = true;
63 Ok(())
64 }
65 LoggingMode::Application => {
66 tracing::debug!("使用应用模式,初始化 subscriber");
67 let guard = self.init_subscriber()?;
68 self._file_guard = guard;
69 *self.initialized.write() = true;
70 Ok(())
71 }
72 }
73 }
74
75 fn init_subscriber(&self) -> Result<Option<tracing_appender::non_blocking::WorkerGuard>> {
76 use tracing_subscriber::filter::EnvFilter;
77 use tracing_subscriber::layer::SubscriberExt;
78 use tracing_subscriber::util::SubscriberInitExt;
79 use tracing_subscriber::Layer;
80
81 let _ = tracing_log::LogTracer::init();
83
84 let env_filter = EnvFilter::try_from_default_env()
85 .unwrap_or_else(|_| EnvFilter::new(self.config.level.to_string()));
86
87 let console_layer = if self.config.console_output {
89 let base = tracing_subscriber::fmt::layer()
90 .with_target(self.config.console_show_target)
91 .with_file(self.config.console_show_file)
92 .with_line_number(self.config.console_show_line)
93 .with_ansi(self.config.console_colors);
94
95 Some(match self.config.console_format {
96 LogFormat::Json => base.json().boxed(),
97 LogFormat::Human => base.boxed(),
98 })
99 } else {
100 None
101 };
102
103 let mut guard: Option<tracing_appender::non_blocking::WorkerGuard> = None;
105 let file_layer = if self.config.file_output {
106 if let Some(path) = self.config.log_file.as_ref() {
107 let (writer, g) = self.build_file_writer(path)?;
108 guard = Some(g);
109
110 let base = tracing_subscriber::fmt::layer().with_writer(writer);
111 Some(match self.config.file_format {
112 LogFormat::Json => base.json().boxed(),
113 LogFormat::Human => base.boxed(),
114 })
115 } else {
116 None
117 }
118 } else {
119 None
120 };
121
122 let subscriber = tracing_subscriber::registry()
123 .with(env_filter)
124 .with(console_layer)
125 .with(file_layer);
126
127 let _ = subscriber.try_init();
129
130 Ok(guard)
131 }
132
133 fn build_file_writer(
134 &self,
135 log_file: &Path,
136 ) -> Result<(
137 tracing_appender::non_blocking::NonBlocking,
138 tracing_appender::non_blocking::WorkerGuard,
139 )> {
140 use tracing_appender::{non_blocking, rolling};
141
142 let dir = log_file.parent().unwrap_or_else(|| Path::new("."));
143 let stem = log_file
144 .file_stem()
145 .and_then(|s| s.to_str())
146 .unwrap_or("app");
147
148 let appender = match self.config.rotation.strategy {
149 RotationStrategy::Daily => rolling::daily(dir, stem),
150 RotationStrategy::Size(_) => {
151 rolling::hourly(dir, stem)
153 }
154 };
155
156 let (nb, guard) = non_blocking(appender);
157 Ok((nb, guard))
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use tracing_subscriber::layer::SubscriberExt;
165 use tracing_subscriber::util::SubscriberInitExt;
166
167 #[test]
168 fn can_force_library_mode_without_panicking() {
169 let _ = tracing_subscriber::registry()
171 .with(tracing_subscriber::fmt::layer())
172 .try_init();
173
174 let mut logger = Logger::new_with_mode(LogConfig::default(), LoggingMode::Library);
175 logger.init().unwrap();
176 }
177
178 #[test]
179 fn can_try_application_mode_init_idempotently() {
180 let mut cfg = LogConfig::default();
182 cfg.console_output = true;
183 cfg.file_output = false;
184
185 let mut logger = Logger::new_with_mode(cfg, LoggingMode::Application);
186 logger.init().unwrap();
187 logger.init().unwrap();
188 }
189}