1use crate::writers::{FileWriter, StdWriter};
5use std::sync::{LazyLock, Mutex};
6use tracing::subscriber::DefaultGuard;
7use tracing_subscriber::filter::LevelFilter;
8use tracing_subscriber::layer::{Layered, SubscriberExt};
9use tracing_subscriber::reload::Handle;
10use tracing_subscriber::{fmt, reload, EnvFilter, Layer, Registry};
11
12pub type Error = String;
13
14#[repr(C)]
16#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
17pub enum LogEventLevel {
18 Trace = 0,
22 Debug = 1,
26 Info = 2,
30 Warn = 3,
34 Error = 4,
38}
39
40pub struct FileConfig {
42 pub path: String,
44 pub max_size_bytes: u64,
47 pub max_files: u64,
51}
52
53#[repr(C)]
55#[derive(Debug, Clone, Copy)]
56pub enum StdTarget {
57 Out,
59 Err,
61}
62
63pub struct StdConfig {
65 pub target: StdTarget,
67}
68
69struct Logger {
71 #[allow(clippy::type_complexity)]
75 layer_handle: Handle<
76 Vec<Box<dyn Layer<Layered<reload::Layer<EnvFilter, Registry>, Registry>> + Send + Sync>>,
77 Layered<reload::Layer<EnvFilter, Registry>, Registry>,
78 >,
79 filter_handle: Handle<EnvFilter, Registry>,
81 #[allow(dead_code)]
83 _guard: Option<DefaultGuard>,
84 file_config: Option<FileConfig>,
86 std_config: Option<StdConfig>,
88}
89
90impl Logger {
91 #[cfg(test)]
92 fn setup() -> Result<Self, Error> {
93 Self::setup_with_global(false)
94 }
95
96 fn setup_global() -> Result<Self, Error> {
97 Self::setup_with_global(true)
98 }
99
100 fn setup_with_global(global: bool) -> Result<Self, Error> {
101 let layers = vec![];
102 let env_filter = env_filter();
103 let (filter_layer, filter_handle) = reload::Layer::new(env_filter);
104 let (layers_layer, layer_handle) = reload::Layer::new(layers);
105
106 let subscriber = tracing_subscriber::registry()
107 .with(filter_layer)
108 .with(layers_layer);
109
110 if global {
111 match tracing::subscriber::set_global_default(subscriber) {
112 Ok(_) => Ok(Self {
113 layer_handle,
114 filter_handle,
115 _guard: None,
116 file_config: None,
117 std_config: None,
118 }),
119
120 Err(_e) => Err(Error::from("Failed to set global default subscriber")),
121 }
122 } else {
123 Ok(Self {
124 layer_handle,
125 filter_handle,
126 _guard: Some(tracing::subscriber::set_default(subscriber)),
127 file_config: None,
128 std_config: None,
129 })
130 }
131 }
132
133 fn configure(&self) -> Result<(), Error> {
134 self.layer_handle
135 .modify(|layers| {
136 layers.clear();
141
142 if let Some(file_config) = &self.file_config {
144 if let Ok(file_layer) = file_layer(file_config) {
145 layers.push(file_layer);
146 }
147 }
148
149 if let Some(std_config) = &self.std_config {
150 if let Ok(std_layer) = std_layer(std_config) {
151 layers.push(std_layer);
152 }
153 }
154 })
155 .map_err(|e| Error::from(format!("Failed to update logger configuration: {e}")))?;
156
157 Ok(())
158 }
159
160 fn disable_file(&mut self) -> Result<(), Error> {
161 self.file_config = None;
162 self.configure()
163 }
164
165 fn configure_file(&mut self, file_config: FileConfig) -> Result<(), Error> {
166 self.file_config = Some(file_config);
167 self.configure()
168 }
169
170 fn disable_std(&mut self) -> Result<(), Error> {
171 self.std_config = None;
172 self.configure()
173 }
174
175 fn configure_std(&mut self, std_config: StdConfig) -> Result<(), Error> {
176 self.std_config = Some(std_config);
177 self.configure()
178 }
179
180 fn set_log_level(&self, log_level: LogEventLevel) -> Result<(), Error> {
182 let level_filter = LevelFilter::from(log_level);
183 let new_filter = EnvFilter::try_from_default_env()
184 .unwrap_or_else(|_| EnvFilter::new(level_filter.to_string().to_lowercase()));
185
186 self.filter_handle
187 .modify(|filter| {
188 *filter = new_filter;
189 })
190 .map_err(|e| Error::from(format!("Failed to update log level: {e}")))?;
191
192 Ok(())
193 }
194}
195
196fn env_filter() -> EnvFilter {
198 EnvFilter::try_from_default_env()
199 .unwrap_or_else(|_| EnvFilter::new(LevelFilter::INFO.to_string().to_lowercase()))
200}
201
202#[allow(clippy::type_complexity)]
204fn std_layer(
205 config: &StdConfig,
206) -> Result<
207 Box<dyn Layer<Layered<reload::Layer<EnvFilter, Registry>, Registry>> + Send + Sync + 'static>,
208 Error,
209> {
210 let writer = StdWriter::new(config.target);
211
212 Ok(fmt::layer()
213 .with_writer(writer)
214 .with_thread_ids(true)
215 .with_thread_names(true)
216 .with_target(true)
217 .with_file(true)
218 .with_line_number(true)
219 .with_ansi(false)
220 .boxed())
221}
222
223#[allow(clippy::type_complexity)]
224fn file_layer(
225 config: &FileConfig,
226) -> Result<
227 Box<dyn Layer<Layered<reload::Layer<EnvFilter, Registry>, Registry>> + Send + Sync + 'static>,
228 Error,
229> {
230 let writer = FileWriter::new(config)
231 .map_err(|e| Error::from(format!("Failed to create file writer: {e}")))?;
232
233 Ok(fmt::layer()
234 .with_writer(writer)
235 .with_thread_ids(true)
236 .with_thread_names(true)
237 .with_target(true)
238 .with_file(true)
239 .with_line_number(true)
240 .with_ansi(false)
241 .json()
242 .boxed())
243}
244
245impl From<LogEventLevel> for LevelFilter {
246 fn from(level: LogEventLevel) -> Self {
247 match level {
248 LogEventLevel::Trace => LevelFilter::TRACE,
249 LogEventLevel::Debug => LevelFilter::DEBUG,
250 LogEventLevel::Info => LevelFilter::INFO,
251 LogEventLevel::Warn => LevelFilter::WARN,
252 LogEventLevel::Error => LevelFilter::ERROR,
253 }
254 }
255}
256
257static LOGGER: LazyLock<Mutex<Option<Logger>>> = LazyLock::new(|| Mutex::new(None));
258
259pub fn logger_configure_file(file_config: FileConfig) -> Result<(), Error> {
264 let logger_mutex = &LOGGER;
265 let mut logger_guard = logger_mutex
266 .lock()
267 .map_err(|e| Error::from(format!("Failed to acquire logger lock: {e}")))?;
268
269 if let Some(logger) = logger_guard.as_mut() {
270 logger.configure_file(file_config)
271 } else {
272 let mut logger = Logger::setup_global()?;
273 logger.configure_file(file_config)?;
274 *logger_guard = Some(logger);
275 Ok(())
276 }
277}
278
279pub fn logger_disable_file() -> Result<(), Error> {
283 let logger_mutex = &LOGGER;
284 let mut logger_guard = logger_mutex
285 .lock()
286 .map_err(|e| Error::from(format!("Failed to acquire logger lock: {e}")))?;
287
288 if let Some(logger) = logger_guard.as_mut() {
289 logger.disable_file()
290 } else {
291 Err(Error::from("Logger not initialized"))
292 }
293}
294
295pub fn logger_configure_std(std_config: StdConfig) -> Result<(), Error> {
300 let logger_mutex = &LOGGER;
301 let mut logger_guard = logger_mutex
302 .lock()
303 .map_err(|e| Error::from(format!("Failed to acquire logger lock: {e}")))?;
304
305 if let Some(logger) = logger_guard.as_mut() {
306 logger.configure_std(std_config)
307 } else {
308 let mut logger = Logger::setup_global()?;
309 logger.configure_std(std_config)?;
310 *logger_guard = Some(logger);
311 Ok(())
312 }
313}
314
315pub fn logger_disable_std() -> Result<(), Error> {
319 let logger_mutex = &LOGGER;
320 let mut logger_guard = logger_mutex
321 .lock()
322 .map_err(|e| Error::from(format!("Failed to acquire logger lock: {e}")))?;
323
324 if let Some(logger) = logger_guard.as_mut() {
325 logger.disable_std()
326 } else {
327 Err(Error::from("Logger not initialized"))
328 }
329}
330
331pub fn logger_set_log_level(log_level: LogEventLevel) -> Result<(), Error> {
336 let logger_mutex = &LOGGER;
337 let logger_guard = logger_mutex
338 .lock()
339 .map_err(|e| Error::from(format!("Failed to acquire logger lock: {e}")))?;
340
341 if let Some(logger) = logger_guard.as_ref() {
342 logger.set_log_level(log_level)
343 } else {
344 Err(Error::from("Logger not initialized"))
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use std::sync::{Arc, Mutex};
351 use tempfile::TempDir;
352 use tracing::field::{Field, Visit};
353 use tracing::subscriber::Interest;
354 use tracing::{debug, error, info, trace, warn, Event, Metadata, Subscriber};
355 use tracing_subscriber::layer::{Context, Layer};
356
357 use super::*;
358
359 #[derive(Default)]
360 struct MessageVisitor {
361 message: Option<String>,
362 all_fields: std::collections::HashMap<String, String>,
363 }
364
365 impl Visit for MessageVisitor {
366 fn record_i64(&mut self, field: &Field, value: i64) {
367 let field_name = field.name();
368 let field_value = value.to_string();
369 self.all_fields
370 .insert(field_name.to_string(), field_value.clone());
371
372 if field_name == "message" {
373 self.message = Some(field_value);
374 }
375 }
376
377 fn record_u64(&mut self, field: &Field, value: u64) {
378 let field_name = field.name();
379 let field_value = value.to_string();
380 self.all_fields
381 .insert(field_name.to_string(), field_value.clone());
382
383 if field_name == "message" {
384 self.message = Some(field_value);
385 }
386 }
387
388 fn record_bool(&mut self, field: &Field, value: bool) {
389 let field_name = field.name();
390 let field_value = value.to_string();
391 self.all_fields
392 .insert(field_name.to_string(), field_value.clone());
393
394 if field_name == "message" {
395 self.message = Some(field_value);
396 }
397 }
398
399 fn record_str(&mut self, field: &Field, value: &str) {
400 let field_name = field.name();
401 self.all_fields
402 .insert(field_name.to_string(), value.to_string());
403
404 if field_name == "message" {
405 self.message = Some(value.to_string());
406 }
407 }
408
409 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
410 let field_name = field.name();
411 let field_value = format!("{value:?}");
412 self.all_fields
413 .insert(field_name.to_string(), field_value.clone());
414
415 if field_name == "message" {
416 self.message = Some(field_value);
417 }
418 }
419 }
420
421 #[derive(Default)]
422 struct RecordingLayer<S> {
423 events: Arc<Mutex<Vec<String>>>,
424 _subscriber: std::marker::PhantomData<S>,
425 }
426
427 impl<S> RecordingLayer<S> {
428 fn new(events: Arc<Mutex<Vec<String>>>) -> Self {
429 RecordingLayer {
430 events,
431 _subscriber: std::marker::PhantomData,
432 }
433 }
434 }
435
436 impl<S> Layer<S> for RecordingLayer<S>
437 where
438 S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
439 {
440 fn register_callsite(&self, _metadata: &'static Metadata<'static>) -> Interest {
441 Interest::always()
442 }
443
444 fn enabled(&self, _metadata: &Metadata<'_>, _ctx: Context<'_, S>) -> bool {
445 true
446 }
447
448 fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
449 let mut visitor = MessageVisitor::default();
450 event.record(&mut visitor);
451
452 let mut events = self.events.lock().unwrap();
453 let message = visitor.message.unwrap_or_else(|| {
454 if !visitor.all_fields.is_empty() {
456 format!("Fields: {:?}", visitor.all_fields)
457 } else {
458 format!(
459 "Event: {} - {}",
460 event.metadata().target(),
461 event.metadata().name()
462 )
463 }
464 });
465 events.push(message);
466 }
467 }
468 #[test]
469 #[cfg_attr(miri, ignore)]
470 fn test_logger_setup() {
471 let logger = Logger::setup();
472 assert!(logger.is_ok(), "Logger setup should succeed");
473 }
474
475 #[test]
476 #[cfg_attr(miri, ignore)]
477 fn test_logger_with_std() {
478 let events: Arc<Mutex<Vec<String>>> = Default::default();
479 let mut logger = Logger::setup().expect("Should setup logger successfully");
480
481 let std_config = StdConfig {
482 target: StdTarget::Out,
483 };
484
485 logger
486 .configure_std(std_config)
487 .expect("Should configure std output");
488
489 logger
491 .layer_handle
492 .modify(|layers| {
493 layers.push(Box::new(RecordingLayer::new(Arc::clone(&events))));
494 })
495 .expect("Should be able to add recording layer");
496
497 logger
498 .set_log_level(LogEventLevel::Info)
499 .expect("Should set log level to Info");
500
501 info!(message = "Std output test message");
502
503 let captured_events = events.lock().unwrap();
504 assert_eq!(
505 captured_events.len(),
506 1,
507 "Should capture message with std output"
508 );
509 assert_eq!(captured_events[0], "Std output test message");
510
511 drop(logger);
512 }
513
514 #[test]
515 #[cfg_attr(miri, ignore)]
516 fn test_logger_with_file() {
517 let events: Arc<Mutex<Vec<String>>> = Default::default();
518 let mut logger = Logger::setup().expect("Should setup logger successfully");
519
520 let temp_dir = TempDir::new().expect("Should create temp directory");
521 let log_path = temp_dir.path().join("test.log");
522
523 let file_config = FileConfig {
524 path: log_path.to_string_lossy().to_string(),
525 max_files: 0,
526 max_size_bytes: 0,
527 };
528
529 logger
530 .configure_file(file_config)
531 .expect("Should configure file output");
532
533 logger
535 .layer_handle
536 .modify(|layers| {
537 layers.push(Box::new(RecordingLayer::new(Arc::clone(&events))));
538 })
539 .expect("Should be able to add recording layer");
540
541 logger
542 .set_log_level(LogEventLevel::Info)
543 .expect("Should set log level to Info");
544
545 info!(message = "File output test message");
546
547 let captured_events = events.lock().unwrap();
548 assert_eq!(
549 captured_events.len(),
550 1,
551 "Should capture message with file output"
552 );
553 assert_eq!(captured_events[0], "File output test message");
554 drop(captured_events);
555
556 assert!(
557 log_path.exists(),
558 "Log file should be created at {log_path:?}"
559 );
560
561 std::thread::sleep(std::time::Duration::from_millis(100));
563
564 if let Ok(content) = std::fs::read_to_string(&log_path) {
565 assert!(
566 !content.is_empty(),
567 "Log file should contain some log output"
568 );
569 }
570
571 drop(logger);
572 }
573
574 #[test]
575 #[cfg_attr(miri, ignore)]
576 fn test_logger_with_std_and_file() {
577 let events: Arc<Mutex<Vec<String>>> = Default::default();
578 let mut logger = Logger::setup().expect("Should setup logger successfully");
579
580 let std_config = StdConfig {
582 target: StdTarget::Err,
583 };
584 logger
585 .configure_std(std_config)
586 .expect("Should configure std output");
587
588 let temp_dir = TempDir::new().expect("Should create temp directory");
589 let log_path = temp_dir.path().join("test.log");
590 let file_config = FileConfig {
591 path: log_path.to_string_lossy().to_string(),
592 max_size_bytes: 0,
593 max_files: 0,
594 };
595 logger
596 .configure_file(file_config)
597 .expect("Should configure file output");
598
599 logger
601 .layer_handle
602 .modify(|layers| {
603 layers.push(Box::new(RecordingLayer::new(Arc::clone(&events))));
604 })
605 .expect("Should be able to add recording layer");
606
607 logger
608 .set_log_level(LogEventLevel::Info)
609 .expect("Should set log level to Info");
610
611 warn!(message = "Std and file output test message");
612
613 let captured_events = events.lock().unwrap();
614 assert_eq!(
615 captured_events.len(),
616 1,
617 "Should capture message with std and file output"
618 );
619 assert_eq!(captured_events[0], "Std and file output test message");
620 drop(captured_events);
621
622 assert!(
624 log_path.exists(),
625 "Log file should be created at {log_path:?}"
626 );
627
628 drop(logger);
629 }
630
631 #[test]
632 #[cfg_attr(miri, ignore)]
633 fn test_logger_level_change() {
634 let events: Arc<Mutex<Vec<String>>> = Default::default();
635 let logger = Logger::setup().expect("Should setup logger successfully");
636
637 logger
639 .layer_handle
640 .modify(|layers| {
641 layers.push(Box::new(RecordingLayer::new(Arc::clone(&events))));
642 })
643 .expect("Should be able to add recording layer");
644
645 logger
647 .set_log_level(LogEventLevel::Trace)
648 .expect("Should set log level to Trace");
649
650 trace!(message = "Trace message");
651 debug!(message = "Debug message");
652 info!(message = "Info message");
653 warn!(message = "Warn message");
654 error!(message = "Error message");
655
656 {
657 let captured_events = events.lock().unwrap();
658 assert_eq!(
659 captured_events.len(),
660 5,
661 "Should capture all 5 messages at TRACE level"
662 );
663 }
664
665 events.lock().unwrap().clear();
667 logger
668 .set_log_level(LogEventLevel::Warn)
669 .expect("Should set log level to Warn");
670
671 trace!(message = "Trace filtered");
672 debug!(message = "Debug filtered");
673 info!(message = "Info filtered");
674 warn!(message = "Warn message");
675 error!(message = "Error message");
676
677 {
678 let captured_events = events.lock().unwrap();
679 assert_eq!(
680 captured_events.len(),
681 2,
682 "Should capture only WARN and ERROR messages"
683 );
684 assert_eq!(captured_events[0], "Warn message");
685 assert_eq!(captured_events[1], "Error message");
686 }
687
688 events.lock().unwrap().clear();
690 logger
691 .set_log_level(LogEventLevel::Error)
692 .expect("Should set log level to Error");
693
694 trace!(message = "Trace filtered");
695 debug!(message = "Debug filtered");
696 info!(message = "Info filtered");
697 warn!(message = "Warn filtered");
698 error!(message = "Error message");
699
700 {
701 let captured_events = events.lock().unwrap();
702 assert_eq!(
703 captured_events.len(),
704 1,
705 "Should capture only ERROR message"
706 );
707 assert_eq!(captured_events[0], "Error message");
708 }
709
710 drop(logger);
711 }
712}