1use std::path::Path;
7use std::sync::OnceLock;
8
9use tracing_appender::rolling::{RollingFileAppender, Rotation};
10use tracing_subscriber::{
11 fmt::{self, format::FmtSpan, time::UtcTime},
12 layer::SubscriberExt,
13 reload,
14 util::SubscriberInitExt,
15 EnvFilter,
16};
17
18use super::config::{LogConfig, LogFormat, LogTarget};
19use super::dynamic::LogLevelController;
20use super::rotation::RotationStrategy;
21use crate::error::Result;
22
23static LOGGING_INITIALIZED: OnceLock<()> = OnceLock::new();
25
26pub fn init_logging(config: &LogConfig) -> Result<()> {
46 if LOGGING_INITIALIZED.get().is_some() {
48 return Ok(());
49 }
50
51 config.validate()?;
53
54 let result = init_logging_internal(config);
55
56 if result.is_ok() {
57 LOGGING_INITIALIZED.get_or_init(|| ());
58 }
59
60 result
61}
62
63fn init_logging_internal(config: &LogConfig) -> Result<()> {
65 let filter = build_env_filter(config);
66 let span_events = build_span_events(config);
67
68 if config.dynamic_level {
70 init_with_dynamic_level(config, filter, span_events)
71 } else {
72 init_static(config, filter, span_events)
73 }
74}
75
76fn init_with_dynamic_level(
78 config: &LogConfig,
79 filter: EnvFilter,
80 span_events: FmtSpan,
81) -> Result<()> {
82 let (filter_layer, reload_handle) = reload::Layer::new(filter);
83
84 match &config.target {
85 LogTarget::Stdout => {
86 init_stdout_with_reload(config, filter_layer, span_events)?;
87 }
88 LogTarget::Stderr => {
89 init_stderr_with_reload(config, filter_layer, span_events)?;
90 }
91 LogTarget::File {
92 directory,
93 filename_prefix,
94 rotation,
95 } => {
96 init_file_with_reload(
97 config,
98 filter_layer,
99 span_events,
100 directory,
101 filename_prefix,
102 rotation,
103 )?;
104 }
105 LogTarget::Multi(targets) => {
106 init_multi_with_reload(config, filter_layer, span_events, targets)?;
109 }
110 }
111
112 let controller = LogLevelController::new(reload_handle, config.level);
114 controller.register_global();
115
116 Ok(())
117}
118
119fn init_stdout_with_reload(
121 config: &LogConfig,
122 filter_layer: reload::Layer<EnvFilter, tracing_subscriber::Registry>,
123 span_events: FmtSpan,
124) -> Result<()> {
125 let registry = tracing_subscriber::registry().with(filter_layer);
126
127 match config.format {
128 LogFormat::Json => {
129 let layer = fmt::layer()
130 .json()
131 .with_timer(UtcTime::rfc_3339())
132 .with_file(config.include_location)
133 .with_line_number(config.include_location)
134 .with_target(config.include_target)
135 .with_thread_ids(config.include_thread_ids)
136 .with_thread_names(config.include_thread_names)
137 .with_span_events(span_events)
138 .with_writer(std::io::stdout);
139 registry.with(layer).init();
140 }
141 LogFormat::Pretty => {
142 let layer = fmt::layer()
143 .pretty()
144 .with_timer(UtcTime::rfc_3339())
145 .with_file(config.include_location)
146 .with_line_number(config.include_location)
147 .with_target(config.include_target)
148 .with_thread_ids(config.include_thread_ids)
149 .with_thread_names(config.include_thread_names)
150 .with_span_events(span_events)
151 .with_ansi(config.ansi_colors)
152 .with_writer(std::io::stdout);
153 registry.with(layer).init();
154 }
155 LogFormat::Compact => {
156 let layer = fmt::layer()
157 .compact()
158 .with_timer(UtcTime::rfc_3339())
159 .with_file(config.include_location)
160 .with_line_number(config.include_location)
161 .with_target(config.include_target)
162 .with_thread_ids(config.include_thread_ids)
163 .with_thread_names(config.include_thread_names)
164 .with_span_events(span_events)
165 .with_ansi(config.ansi_colors)
166 .with_writer(std::io::stdout);
167 registry.with(layer).init();
168 }
169 LogFormat::Full => {
170 let layer = fmt::layer()
171 .with_timer(UtcTime::rfc_3339())
172 .with_file(config.include_location)
173 .with_line_number(config.include_location)
174 .with_target(config.include_target)
175 .with_thread_ids(config.include_thread_ids)
176 .with_thread_names(config.include_thread_names)
177 .with_span_events(span_events)
178 .with_ansi(config.ansi_colors)
179 .with_writer(std::io::stdout);
180 registry.with(layer).init();
181 }
182 }
183
184 Ok(())
185}
186
187fn init_stderr_with_reload(
189 config: &LogConfig,
190 filter_layer: reload::Layer<EnvFilter, tracing_subscriber::Registry>,
191 span_events: FmtSpan,
192) -> Result<()> {
193 let registry = tracing_subscriber::registry().with(filter_layer);
194
195 let layer = fmt::layer()
196 .with_timer(UtcTime::rfc_3339())
197 .with_file(config.include_location)
198 .with_line_number(config.include_location)
199 .with_target(config.include_target)
200 .with_thread_ids(config.include_thread_ids)
201 .with_thread_names(config.include_thread_names)
202 .with_span_events(span_events)
203 .with_ansi(config.ansi_colors)
204 .with_writer(std::io::stderr);
205
206 registry.with(layer).init();
207 Ok(())
208}
209
210fn init_file_with_reload(
212 config: &LogConfig,
213 filter_layer: reload::Layer<EnvFilter, tracing_subscriber::Registry>,
214 span_events: FmtSpan,
215 directory: &Path,
216 filename_prefix: &str,
217 rotation_config: &super::rotation::RotationConfig,
218) -> Result<()> {
219 let rotation = match rotation_config.strategy {
220 RotationStrategy::Daily => Rotation::DAILY,
221 RotationStrategy::Hourly => Rotation::HOURLY,
222 RotationStrategy::Minutely => Rotation::MINUTELY,
223 RotationStrategy::Never => Rotation::NEVER,
224 };
225
226 let file_appender = RollingFileAppender::new(rotation, directory, filename_prefix);
227 let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
228
229 std::mem::forget(_guard);
232
233 let registry = tracing_subscriber::registry().with(filter_layer);
234
235 let layer = fmt::layer()
237 .json()
238 .with_timer(UtcTime::rfc_3339())
239 .with_file(config.include_location)
240 .with_line_number(config.include_location)
241 .with_target(config.include_target)
242 .with_thread_ids(config.include_thread_ids)
243 .with_thread_names(config.include_thread_names)
244 .with_span_events(span_events)
245 .with_ansi(false) .with_writer(non_blocking);
247
248 registry.with(layer).init();
249 Ok(())
250}
251
252fn init_multi_with_reload(
254 config: &LogConfig,
255 filter_layer: reload::Layer<EnvFilter, tracing_subscriber::Registry>,
256 span_events: FmtSpan,
257 targets: &[LogTarget],
258) -> Result<()> {
259 let has_console = targets
263 .iter()
264 .any(|t| matches!(t, LogTarget::Stdout | LogTarget::Stderr));
265 let file_target = targets.iter().find_map(|t| {
266 if let LogTarget::File {
267 directory,
268 filename_prefix,
269 rotation,
270 } = t
271 {
272 Some((directory.clone(), filename_prefix.clone(), rotation.clone()))
273 } else {
274 None
275 }
276 });
277
278 let registry = tracing_subscriber::registry().with(filter_layer);
279
280 if has_console && file_target.is_some() {
281 let (directory, filename_prefix, rotation_config) = file_target.unwrap();
282
283 let rotation = match rotation_config.strategy {
284 RotationStrategy::Daily => Rotation::DAILY,
285 RotationStrategy::Hourly => Rotation::HOURLY,
286 RotationStrategy::Minutely => Rotation::MINUTELY,
287 RotationStrategy::Never => Rotation::NEVER,
288 };
289
290 let file_appender = RollingFileAppender::new(rotation, &directory, &filename_prefix);
291 let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
292 std::mem::forget(_guard);
293
294 let console_layer = fmt::layer()
296 .pretty()
297 .with_timer(UtcTime::rfc_3339())
298 .with_file(config.include_location)
299 .with_line_number(config.include_location)
300 .with_target(config.include_target)
301 .with_span_events(span_events.clone())
302 .with_ansi(config.ansi_colors)
303 .with_writer(std::io::stdout);
304
305 let file_layer = fmt::layer()
307 .json()
308 .with_timer(UtcTime::rfc_3339())
309 .with_file(config.include_location)
310 .with_line_number(config.include_location)
311 .with_target(config.include_target)
312 .with_span_events(span_events)
313 .with_ansi(false)
314 .with_writer(non_blocking);
315
316 registry.with(console_layer).with(file_layer).init();
317 } else if has_console {
318 init_stdout_with_reload(
320 config,
321 reload::Layer::new(build_env_filter(config)).0,
322 span_events,
323 )?;
324 } else if let Some((directory, filename_prefix, rotation_config)) = file_target {
325 let reload_layer = reload::Layer::new(build_env_filter(config)).0;
327 init_file_with_reload(
328 config,
329 reload_layer,
330 span_events,
331 &directory,
332 &filename_prefix,
333 &rotation_config,
334 )?;
335 }
336
337 Ok(())
338}
339
340fn init_static(config: &LogConfig, filter: EnvFilter, span_events: FmtSpan) -> Result<()> {
342 match &config.target {
343 LogTarget::Stdout => init_stdout_static(config, filter, span_events),
344 LogTarget::Stderr => init_stderr_static(config, filter, span_events),
345 LogTarget::File {
346 directory,
347 filename_prefix,
348 rotation,
349 } => init_file_static(
350 config,
351 filter,
352 span_events,
353 directory,
354 filename_prefix,
355 rotation,
356 ),
357 LogTarget::Multi(_) => {
358 init_with_dynamic_level(config, filter, span_events)
361 }
362 }
363}
364
365fn init_stdout_static(config: &LogConfig, filter: EnvFilter, span_events: FmtSpan) -> Result<()> {
367 let registry = tracing_subscriber::registry().with(filter);
368
369 match config.format {
370 LogFormat::Json => {
371 let layer = fmt::layer()
372 .json()
373 .with_timer(UtcTime::rfc_3339())
374 .with_file(config.include_location)
375 .with_line_number(config.include_location)
376 .with_target(config.include_target)
377 .with_thread_ids(config.include_thread_ids)
378 .with_thread_names(config.include_thread_names)
379 .with_span_events(span_events)
380 .with_writer(std::io::stdout);
381 registry.with(layer).init();
382 }
383 LogFormat::Pretty => {
384 let layer = fmt::layer()
385 .pretty()
386 .with_timer(UtcTime::rfc_3339())
387 .with_file(config.include_location)
388 .with_line_number(config.include_location)
389 .with_target(config.include_target)
390 .with_thread_ids(config.include_thread_ids)
391 .with_thread_names(config.include_thread_names)
392 .with_span_events(span_events)
393 .with_ansi(config.ansi_colors)
394 .with_writer(std::io::stdout);
395 registry.with(layer).init();
396 }
397 LogFormat::Compact => {
398 let layer = fmt::layer()
399 .compact()
400 .with_timer(UtcTime::rfc_3339())
401 .with_file(config.include_location)
402 .with_line_number(config.include_location)
403 .with_target(config.include_target)
404 .with_thread_ids(config.include_thread_ids)
405 .with_thread_names(config.include_thread_names)
406 .with_span_events(span_events)
407 .with_ansi(config.ansi_colors)
408 .with_writer(std::io::stdout);
409 registry.with(layer).init();
410 }
411 LogFormat::Full => {
412 let layer = fmt::layer()
413 .with_timer(UtcTime::rfc_3339())
414 .with_file(config.include_location)
415 .with_line_number(config.include_location)
416 .with_target(config.include_target)
417 .with_thread_ids(config.include_thread_ids)
418 .with_thread_names(config.include_thread_names)
419 .with_span_events(span_events)
420 .with_ansi(config.ansi_colors)
421 .with_writer(std::io::stdout);
422 registry.with(layer).init();
423 }
424 }
425
426 Ok(())
427}
428
429fn init_stderr_static(config: &LogConfig, filter: EnvFilter, span_events: FmtSpan) -> Result<()> {
431 let registry = tracing_subscriber::registry().with(filter);
432
433 let layer = fmt::layer()
434 .with_timer(UtcTime::rfc_3339())
435 .with_file(config.include_location)
436 .with_line_number(config.include_location)
437 .with_target(config.include_target)
438 .with_thread_ids(config.include_thread_ids)
439 .with_thread_names(config.include_thread_names)
440 .with_span_events(span_events)
441 .with_ansi(config.ansi_colors)
442 .with_writer(std::io::stderr);
443
444 registry.with(layer).init();
445 Ok(())
446}
447
448fn init_file_static(
450 config: &LogConfig,
451 filter: EnvFilter,
452 span_events: FmtSpan,
453 directory: &Path,
454 filename_prefix: &str,
455 rotation_config: &super::rotation::RotationConfig,
456) -> Result<()> {
457 let rotation = match rotation_config.strategy {
458 RotationStrategy::Daily => Rotation::DAILY,
459 RotationStrategy::Hourly => Rotation::HOURLY,
460 RotationStrategy::Minutely => Rotation::MINUTELY,
461 RotationStrategy::Never => Rotation::NEVER,
462 };
463
464 let file_appender = RollingFileAppender::new(rotation, directory, filename_prefix);
465 let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
466 std::mem::forget(_guard);
467
468 let registry = tracing_subscriber::registry().with(filter);
469
470 let layer = fmt::layer()
471 .json()
472 .with_timer(UtcTime::rfc_3339())
473 .with_file(config.include_location)
474 .with_line_number(config.include_location)
475 .with_target(config.include_target)
476 .with_thread_ids(config.include_thread_ids)
477 .with_thread_names(config.include_thread_names)
478 .with_span_events(span_events)
479 .with_ansi(false)
480 .with_writer(non_blocking);
481
482 registry.with(layer).init();
483 Ok(())
484}
485
486fn build_env_filter(config: &LogConfig) -> EnvFilter {
488 EnvFilter::try_from_default_env().unwrap_or_else(|_| {
490 let filter_str = config.build_filter_string();
491 EnvFilter::try_new(&filter_str)
492 .unwrap_or_else(|_| EnvFilter::new(config.level.as_filter_str()))
493 })
494}
495
496fn build_span_events(config: &LogConfig) -> FmtSpan {
498 if config.include_span_events {
499 FmtSpan::ENTER | FmtSpan::EXIT
500 } else {
501 FmtSpan::NONE
502 }
503}
504
505pub fn init_test_logging() {
510 let _ = init_logging(&LogConfig::test());
511}
512
513pub fn is_logging_initialized() -> bool {
515 LOGGING_INITIALIZED.get().is_some()
516}
517
518#[cfg(test)]
519mod tests {
520 use super::super::config::LogLevel;
521 use super::*;
522
523 #[test]
524 fn test_build_env_filter() {
525 let config = LogConfig::builder().level(LogLevel::Debug).build();
526 let filter = build_env_filter(&config);
527 drop(filter);
529 }
530
531 #[test]
532 fn test_build_span_events() {
533 let config = LogConfig::builder().include_span_events(true).build();
534 let span_events = build_span_events(&config);
535 assert_ne!(span_events, FmtSpan::NONE);
537
538 let config = LogConfig::builder().include_span_events(false).build();
539 let span_events = build_span_events(&config);
540 assert_eq!(span_events, FmtSpan::NONE);
541 }
542}