1use crate::{LogEntry, LogLevel, Logger};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::sync::{Arc, Mutex};
18use std::time::{Duration, Instant};
19
20#[derive(Debug, Clone)]
26pub enum RotationPolicy {
27 Size(u64),
29 Time(Duration),
31 SizeOrTime(u64, Duration),
33}
34
35impl RotationPolicy {
36 fn should_rotate(&self, current_size: u64, elapsed: Duration) -> bool {
42 match self {
43 RotationPolicy::Size(max_bytes) => current_size >= *max_bytes,
44 RotationPolicy::Time(max_age) => elapsed >= *max_age,
45 RotationPolicy::SizeOrTime(max_bytes, max_age) => {
46 current_size >= *max_bytes || elapsed >= *max_age
47 }
48 }
49 }
50}
51
52pub struct LogRotator {
61 policy: RotationPolicy,
63 max_files: usize,
65 current: Mutex<Vec<u8>>,
67 rotated: Mutex<Vec<Vec<u8>>>,
69 started_at: Mutex<Instant>,
71 rotation_count: Mutex<u64>,
73}
74
75impl LogRotator {
76 pub fn new(policy: RotationPolicy, max_files: usize) -> Self {
82 Self {
83 policy,
84 max_files,
85 current: Mutex::new(Vec::new()),
86 rotated: Mutex::new(Vec::new()),
87 started_at: Mutex::new(Instant::now()),
88 rotation_count: Mutex::new(0),
89 }
90 }
91
92 pub fn write(&self, data: &[u8]) {
94 let should_rotate = {
95 let mut current = self.current.lock().unwrap();
96 current.extend_from_slice(data);
97 let started_at = self.started_at.lock().unwrap();
98 self.policy
99 .should_rotate(current.len() as u64, started_at.elapsed())
100 };
101
102 if should_rotate {
103 self.rotate();
104 }
105 }
106
107 pub fn rotate(&self) {
109 let mut current = self.current.lock().unwrap();
110 let mut rotated = self.rotated.lock().unwrap();
111 let mut started_at = self.started_at.lock().unwrap();
112 let mut count = self.rotation_count.lock().unwrap();
113
114 let old_buffer = std::mem::take(&mut *current);
116 if !old_buffer.is_empty() {
117 rotated.insert(0, old_buffer);
118 }
119
120 while rotated.len() > self.max_files {
122 rotated.pop();
123 }
124
125 *started_at = Instant::now();
127 *count += 1;
128 }
129
130 pub fn current_size(&self) -> usize {
132 self.current.lock().unwrap().len()
133 }
134
135 pub fn rotated_count(&self) -> usize {
137 self.rotated.lock().unwrap().len()
138 }
139
140 pub fn total_rotations(&self) -> u64 {
142 *self.rotation_count.lock().unwrap()
143 }
144
145 pub fn current_content(&self) -> Vec<u8> {
147 self.current.lock().unwrap().clone()
148 }
149
150 pub fn rotated_content(&self, index: usize) -> Option<Vec<u8>> {
152 let rotated = self.rotated.lock().unwrap();
153 rotated.get(index).cloned()
154 }
155
156 pub fn current_age(&self) -> Duration {
158 self.started_at.lock().unwrap().elapsed()
159 }
160
161 pub fn policy(&self) -> &RotationPolicy {
163 &self.policy
164 }
165
166 pub fn max_files(&self) -> usize {
168 self.max_files
169 }
170
171 pub fn clear(&self) {
173 self.current.lock().unwrap().clear();
174 self.rotated.lock().unwrap().clear();
175 *self.started_at.lock().unwrap() = Instant::now();
176 }
177}
178
179pub trait LogSink: Send + Sync {
188 fn write(&self, entry: &LogEntry);
190 fn name(&self) -> &str;
192}
193
194pub struct MemorySink {
196 name: String,
197 entries: Mutex<Vec<LogEntry>>,
198}
199
200impl MemorySink {
201 pub fn new(name: impl Into<String>) -> Self {
202 Self {
203 name: name.into(),
204 entries: Mutex::new(Vec::new()),
205 }
206 }
207
208 pub fn entries(&self) -> Vec<LogEntry> {
210 self.entries.lock().unwrap().clone()
211 }
212
213 pub fn len(&self) -> usize {
215 self.entries.lock().unwrap().len()
216 }
217
218 pub fn is_empty(&self) -> bool {
220 self.entries.lock().unwrap().is_empty()
221 }
222
223 pub fn clear(&self) {
225 self.entries.lock().unwrap().clear();
226 }
227}
228
229impl LogSink for MemorySink {
230 fn write(&self, entry: &LogEntry) {
231 if let Ok(mut entries) = self.entries.lock() {
233 entries.push(entry.clone());
234 }
235 }
236
237 fn name(&self) -> &str {
238 &self.name
239 }
240}
241
242pub struct ConsoleSink {
246 name: String,
247}
248
249impl ConsoleSink {
250 pub fn new() -> Self {
251 Self {
252 name: "console".to_string(),
253 }
254 }
255
256 pub fn with_name(name: impl Into<String>) -> Self {
257 Self { name: name.into() }
258 }
259}
260
261impl Default for ConsoleSink {
262 fn default() -> Self {
263 Self::new()
264 }
265}
266
267impl LogSink for ConsoleSink {
268 fn write(&self, entry: &LogEntry) {
269 println!(
270 "[{}] {} - {}",
271 entry.level.as_str(),
272 entry.timestamp,
273 entry.message
274 );
275 }
276
277 fn name(&self) -> &str {
278 &self.name
279 }
280}
281
282pub struct CallbackSink<F>
287where
288 F: Fn(&LogEntry) + Send + Sync,
289{
290 name: String,
291 callback: F,
292}
293
294impl<F> CallbackSink<F>
295where
296 F: Fn(&LogEntry) + Send + Sync,
297{
298 pub fn new(name: impl Into<String>, callback: F) -> Self {
299 Self {
300 name: name.into(),
301 callback,
302 }
303 }
304}
305
306impl<F> LogSink for CallbackSink<F>
307where
308 F: Fn(&LogEntry) + Send + Sync,
309{
310 fn write(&self, entry: &LogEntry) {
311 (self.callback)(entry);
312 }
313
314 fn name(&self) -> &str {
315 &self.name
316 }
317}
318
319pub struct MultiOutputLogger {
324 level: LogLevel,
325 sinks: Vec<Arc<dyn LogSink>>,
326}
327
328impl MultiOutputLogger {
329 pub fn new(level: LogLevel) -> Self {
331 Self {
332 level,
333 sinks: Vec::new(),
334 }
335 }
336
337 pub fn add_sink(&mut self, sink: Arc<dyn LogSink>) -> &mut Self {
339 self.sinks.push(sink);
340 self
341 }
342
343 pub fn sink_names(&self) -> Vec<String> {
345 self.sinks.iter().map(|s| s.name().to_string()).collect()
346 }
347
348 pub fn sink_count(&self) -> usize {
350 self.sinks.len()
351 }
352
353 pub fn level(&self) -> LogLevel {
355 self.level
356 }
357}
358
359impl Logger for MultiOutputLogger {
360 fn log(&self, level: LogLevel, msg: &str) {
361 if level < self.level {
362 return;
363 }
364 let entry = LogEntry {
365 level,
366 message: msg.to_string(),
367 timestamp: chrono::Utc::now().to_rfc3339(),
368 };
369 for sink in &self.sinks {
370 sink.write(&entry);
371 }
372 }
373}
374
375#[derive(Debug, Clone)]
405pub struct LevelFilter {
406 default_level: LogLevel,
408 target_levels: HashMap<String, LogLevel>,
410}
411
412impl LevelFilter {
413 pub fn new(default_level: LogLevel) -> Self {
415 Self {
416 default_level,
417 target_levels: HashMap::new(),
418 }
419 }
420
421 pub fn with_target_level(mut self, target: impl Into<String>, level: LogLevel) -> Self {
423 self.target_levels.insert(target.into(), level);
424 self
425 }
426
427 pub fn remove_target(&mut self, target: &str) -> Option<LogLevel> {
429 self.target_levels.remove(target)
430 }
431
432 pub fn level_for(&self, target: &str) -> LogLevel {
434 self.target_levels
435 .get(target)
436 .copied()
437 .unwrap_or(self.default_level)
438 }
439
440 pub fn default_level(&self) -> LogLevel {
442 self.default_level
443 }
444
445 pub fn set_default_level(&mut self, level: LogLevel) {
447 self.default_level = level;
448 }
449
450 pub fn should_log(&self, target: &str, level: LogLevel) -> bool {
452 level >= self.level_for(target)
453 }
454
455 pub fn target_count(&self) -> usize {
457 self.target_levels.len()
458 }
459
460 pub fn targets(&self) -> Vec<String> {
462 self.target_levels.keys().cloned().collect()
463 }
464}
465
466impl Default for LevelFilter {
467 fn default() -> Self {
468 Self::new(LogLevel::Info)
469 }
470}
471
472pub type StructuredFields = HashMap<String, String>;
481
482#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct StructuredLogEntry {
489 pub level: LogLevel,
490 pub message: String,
491 pub timestamp: String,
492 pub target: Option<String>,
493 pub fields: StructuredFields,
494}
495
496impl StructuredLogEntry {
497 pub fn new(level: LogLevel, message: impl Into<String>) -> Self {
499 Self {
500 level,
501 message: message.into(),
502 timestamp: chrono::Utc::now().to_rfc3339(),
503 target: None,
504 fields: HashMap::new(),
505 }
506 }
507
508 pub fn with_target(mut self, target: impl Into<String>) -> Self {
510 self.target = Some(target.into());
511 self
512 }
513
514 pub fn with_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
516 self.fields.insert(key.into(), value.into());
517 self
518 }
519
520 pub fn from_log_entry(entry: &LogEntry) -> Self {
522 Self {
523 level: entry.level,
524 message: entry.message.clone(),
525 timestamp: entry.timestamp.clone(),
526 target: None,
527 fields: HashMap::new(),
528 }
529 }
530
531 pub fn to_log_entry(&self) -> LogEntry {
533 LogEntry {
534 level: self.level,
535 message: self.message.clone(),
536 timestamp: self.timestamp.clone(),
537 }
538 }
539
540 pub fn to_json(&self) -> Result<String, serde_json::Error> {
542 serde_json::to_string(self)
543 }
544
545 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
547 serde_json::from_str(json)
548 }
549
550 pub fn format_fields(&self) -> String {
552 let mut pairs: Vec<String> = self
553 .fields
554 .iter()
555 .map(|(k, v)| format!("{}={}", k, v))
556 .collect();
557 pairs.sort(); pairs.join(" ")
559 }
560}
561
562pub struct StructuredLogWriter {
569 filter: LevelFilter,
570 sinks: Vec<Arc<dyn StructuredSink>>,
571}
572
573pub trait StructuredSink: Send + Sync {
577 fn write(&self, entry: &StructuredLogEntry);
578 fn name(&self) -> &str;
579}
580
581pub struct MemoryStructuredSink {
583 name: String,
584 entries: Mutex<Vec<StructuredLogEntry>>,
585}
586
587impl MemoryStructuredSink {
588 pub fn new(name: impl Into<String>) -> Self {
589 Self {
590 name: name.into(),
591 entries: Mutex::new(Vec::new()),
592 }
593 }
594
595 pub fn entries(&self) -> Vec<StructuredLogEntry> {
596 self.entries.lock().unwrap().clone()
597 }
598
599 pub fn len(&self) -> usize {
600 self.entries.lock().unwrap().len()
601 }
602
603 pub fn is_empty(&self) -> bool {
604 self.entries.lock().unwrap().is_empty()
605 }
606
607 pub fn clear(&self) {
608 self.entries.lock().unwrap().clear();
609 }
610}
611
612impl StructuredSink for MemoryStructuredSink {
613 fn write(&self, entry: &StructuredLogEntry) {
614 if let Ok(mut entries) = self.entries.lock() {
615 entries.push(entry.clone());
616 }
617 }
618
619 fn name(&self) -> &str {
620 &self.name
621 }
622}
623
624impl StructuredLogWriter {
625 pub fn new(filter: LevelFilter) -> Self {
627 Self {
628 filter,
629 sinks: Vec::new(),
630 }
631 }
632
633 pub fn add_sink(&mut self, sink: Arc<dyn StructuredSink>) -> &mut Self {
635 self.sinks.push(sink);
636 self
637 }
638
639 pub fn sink_count(&self) -> usize {
641 self.sinks.len()
642 }
643
644 pub fn sink_names(&self) -> Vec<String> {
646 self.sinks.iter().map(|s| s.name().to_string()).collect()
647 }
648
649 pub fn filter(&self) -> &LevelFilter {
651 &self.filter
652 }
653
654 pub fn filter_mut(&mut self) -> &mut LevelFilter {
656 &mut self.filter
657 }
658
659 pub fn log(&self, entry: &StructuredLogEntry) {
664 let target = entry.target.as_deref().unwrap_or("");
665 if !self.filter.should_log(target, entry.level) {
666 return;
667 }
668 for sink in &self.sinks {
669 sink.write(entry);
670 }
671 }
672
673 pub fn log_with_fields(
675 &self,
676 target: impl Into<String>,
677 level: LogLevel,
678 message: impl Into<String>,
679 fields: StructuredFields,
680 ) {
681 let entry = StructuredLogEntry::new(level, message)
682 .with_target(target)
683 .with_fields(fields);
684 self.log(&entry);
685 }
686}
687
688impl StructuredLogEntry {
690 pub fn with_fields(mut self, fields: StructuredFields) -> Self {
692 self.fields.extend(fields);
693 self
694 }
695}
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700 use std::thread;
701 use std::time::Duration;
702
703 #[test]
706 fn test_rotation_policy_size_met() {
707 let policy = RotationPolicy::Size(100);
708 assert!(policy.should_rotate(100, Duration::from_secs(0)));
709 assert!(policy.should_rotate(101, Duration::from_secs(0)));
710 assert!(!policy.should_rotate(99, Duration::from_secs(0)));
711 }
712
713 #[test]
714 fn test_rotation_policy_time_met() {
715 let policy = RotationPolicy::Time(Duration::from_secs(60));
716 assert!(policy.should_rotate(0, Duration::from_secs(60)));
717 assert!(policy.should_rotate(0, Duration::from_secs(61)));
718 assert!(!policy.should_rotate(0, Duration::from_secs(59)));
719 }
720
721 #[test]
722 fn test_rotation_policy_size_or_time_either() {
723 let policy = RotationPolicy::SizeOrTime(100, Duration::from_secs(60));
724 assert!(policy.should_rotate(100, Duration::from_secs(0)));
726 assert!(policy.should_rotate(0, Duration::from_secs(60)));
728 assert!(!policy.should_rotate(99, Duration::from_secs(59)));
730 assert!(policy.should_rotate(200, Duration::from_secs(120)));
732 }
733
734 #[test]
737 fn test_log_rotator_new_empty() {
738 let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
739 assert_eq!(rotator.current_size(), 0);
740 assert_eq!(rotator.rotated_count(), 0);
741 assert_eq!(rotator.total_rotations(), 0);
742 assert_eq!(rotator.max_files(), 3);
743 }
744
745 #[test]
746 fn test_log_rotator_write_accumulates() {
747 let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
748 rotator.write(b"hello");
749 rotator.write(b" world");
750 assert_eq!(rotator.current_size(), 11);
751 assert_eq!(rotator.current_content(), b"hello world");
752 }
753
754 #[test]
755 fn test_log_rotator_size_triggers_rotation() {
756 let rotator = LogRotator::new(RotationPolicy::Size(10), 3);
757 rotator.write(b"12345"); assert_eq!(rotator.rotated_count(), 0);
759 rotator.write(b"67890"); assert_eq!(rotator.rotated_count(), 1);
761 assert_eq!(rotator.total_rotations(), 1);
762 assert_eq!(rotator.current_size(), 0);
763 }
764
765 #[test]
766 fn test_log_rotator_rotation_preserves_content() {
767 let rotator = LogRotator::new(RotationPolicy::Size(10), 3);
768 rotator.write(b"hello world"); assert_eq!(rotator.rotated_count(), 1);
770 let rotated = rotator.rotated_content(0).expect("rotated[0] must exist");
771 assert_eq!(rotated, b"hello world");
772 }
773
774 #[test]
775 fn test_log_rotator_max_files_drops_oldest() {
776 let rotator = LogRotator::new(RotationPolicy::Size(5), 2);
777 rotator.write(b"AAAAAA"); rotator.write(b"BBBBBB"); rotator.write(b"CCCCCC"); assert_eq!(rotator.rotated_count(), 2);
781 assert_eq!(rotator.total_rotations(), 3);
782 assert_eq!(rotator.rotated_content(0), Some(b"CCCCCC".to_vec()));
784 assert_eq!(rotator.rotated_content(1), Some(b"BBBBBB".to_vec()));
785 assert_eq!(rotator.rotated_content(2), None);
786 }
787
788 #[test]
789 fn test_log_rotator_manual_rotate() {
790 let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
791 rotator.write(b"some data");
792 rotator.rotate();
793 assert_eq!(rotator.rotated_count(), 1);
794 assert_eq!(rotator.current_size(), 0);
795 }
796
797 #[test]
798 fn test_log_rotator_manual_rotate_empty_no_op() {
799 let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
800 rotator.rotate(); assert_eq!(rotator.rotated_count(), 0);
802 assert_eq!(rotator.total_rotations(), 1); }
804
805 #[test]
806 fn test_log_rotator_clear() {
807 let rotator = LogRotator::new(RotationPolicy::Size(5), 3);
808 rotator.write(b"hello world"); rotator.write(b"more");
810 assert!(!rotator.current_content().is_empty());
811 assert_eq!(rotator.rotated_count(), 1);
812
813 rotator.clear();
814 assert_eq!(rotator.current_size(), 0);
815 assert_eq!(rotator.rotated_count(), 0);
816 }
817
818 #[test]
819 fn test_log_rotator_policy_accessor() {
820 let rotator = LogRotator::new(RotationPolicy::Size(256), 5);
821 match rotator.policy() {
822 RotationPolicy::Size(n) => assert_eq!(*n, 256),
823 _ => panic!("expected Size policy"),
824 }
825 }
826
827 #[test]
828 fn test_log_rotator_time_based_rotation() {
829 let rotator = LogRotator::new(RotationPolicy::Time(Duration::from_millis(50)), 3);
830 rotator.write(b"data");
831 assert_eq!(rotator.rotated_count(), 0);
832 thread::sleep(Duration::from_millis(60));
833 rotator.write(b"more"); assert_eq!(rotator.rotated_count(), 1);
835 }
836
837 #[test]
838 fn test_log_rotator_current_age_increases() {
839 let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
840 let age1 = rotator.current_age();
841 thread::sleep(Duration::from_millis(10));
842 let age2 = rotator.current_age();
843 assert!(age2 > age1);
844 }
845
846 #[test]
849 fn test_memory_sink_new_empty() {
850 let sink = MemorySink::new("test");
851 assert_eq!(sink.name(), "test");
852 assert!(sink.is_empty());
853 assert_eq!(sink.len(), 0);
854 }
855
856 #[test]
857 fn test_memory_sink_write_stores_entry() {
858 let sink = MemorySink::new("mem");
859 let entry = LogEntry {
860 level: LogLevel::Info,
861 message: "hello".to_string(),
862 timestamp: "2024-01-01T00:00:00Z".to_string(),
863 };
864 sink.write(&entry);
865 assert_eq!(sink.len(), 1);
866 assert_eq!(sink.entries()[0].message, "hello");
867 }
868
869 #[test]
870 fn test_memory_sink_clear() {
871 let sink = MemorySink::new("mem");
872 let entry = LogEntry {
873 level: LogLevel::Info,
874 message: "x".to_string(),
875 timestamp: "t".to_string(),
876 };
877 sink.write(&entry);
878 sink.write(&entry);
879 assert_eq!(sink.len(), 2);
880 sink.clear();
881 assert!(sink.is_empty());
882 }
883
884 #[test]
887 fn test_console_sink_name_default() {
888 let sink = ConsoleSink::new();
889 assert_eq!(sink.name(), "console");
890 }
891
892 #[test]
893 fn test_console_sink_custom_name() {
894 let sink = ConsoleSink::with_name("stdout");
895 assert_eq!(sink.name(), "stdout");
896 }
897
898 #[test]
899 fn test_console_sink_write_does_not_panic() {
900 let sink = ConsoleSink::new();
901 let entry = LogEntry {
902 level: LogLevel::Info,
903 message: "test".to_string(),
904 timestamp: "t".to_string(),
905 };
906 sink.write(&entry); }
908
909 #[test]
912 fn test_callback_sink_invokes_closure() {
913 let counter = Arc::new(Mutex::new(0u32));
914 let c = counter.clone();
915 let sink = CallbackSink::new("cb", move |_entry| {
916 *c.lock().unwrap() += 1;
917 });
918 let entry = LogEntry {
919 level: LogLevel::Info,
920 message: "x".to_string(),
921 timestamp: "t".to_string(),
922 };
923 sink.write(&entry);
924 sink.write(&entry);
925 assert_eq!(*counter.lock().unwrap(), 2);
926 }
927
928 #[test]
931 fn test_multi_output_logger_fans_out_to_all_sinks() {
932 let mut logger = MultiOutputLogger::new(LogLevel::Debug);
933 let sink1 = Arc::new(MemorySink::new("s1"));
934 let sink2 = Arc::new(MemorySink::new("s2"));
935
936 logger.add_sink(sink1.clone());
937 logger.add_sink(sink2.clone());
938
939 logger.log(LogLevel::Info, "hello");
940
941 assert_eq!(sink1.len(), 1);
942 assert_eq!(sink2.len(), 1);
943 assert_eq!(sink1.entries()[0].message, "hello");
944 assert_eq!(sink2.entries()[0].message, "hello");
945 }
946
947 #[test]
948 fn test_multi_output_logger_respects_level_filter() {
949 let mut logger = MultiOutputLogger::new(LogLevel::Warn);
950 let sink = Arc::new(MemorySink::new("s"));
951 logger.add_sink(sink.clone());
952
953 logger.log(LogLevel::Debug, "debug"); logger.log(LogLevel::Info, "info"); logger.log(LogLevel::Warn, "warn"); logger.log(LogLevel::Error, "error"); assert_eq!(sink.len(), 2);
959 }
960
961 #[test]
962 fn test_multi_output_logger_sink_names() {
963 let mut logger = MultiOutputLogger::new(LogLevel::Info);
964 logger.add_sink(Arc::new(MemorySink::new("alpha")));
965 logger.add_sink(Arc::new(MemorySink::new("beta")));
966 let names = logger.sink_names();
967 assert_eq!(names, vec!["alpha", "beta"]);
968 assert_eq!(logger.sink_count(), 2);
969 }
970
971 #[test]
972 fn test_multi_output_logger_empty_sinks_no_error() {
973 let logger = MultiOutputLogger::new(LogLevel::Info);
974 logger.log(LogLevel::Info, "msg"); assert_eq!(logger.sink_count(), 0);
976 }
977
978 #[test]
979 fn test_multi_output_logger_level_accessor() {
980 let logger = MultiOutputLogger::new(LogLevel::Error);
981 assert_eq!(logger.level(), LogLevel::Error);
982 }
983
984 #[test]
987 fn test_level_filter_default_level() {
988 let filter = LevelFilter::new(LogLevel::Info);
989 assert_eq!(filter.default_level(), LogLevel::Info);
990 assert_eq!(filter.level_for("anything"), LogLevel::Info);
991 assert!(filter.should_log("anything", LogLevel::Info));
992 assert!(!filter.should_log("anything", LogLevel::Debug));
993 }
994
995 #[test]
996 fn test_level_filter_target_override() {
997 let filter = LevelFilter::new(LogLevel::Info)
998 .with_target_level("database", LogLevel::Debug)
999 .with_target_level("http", LogLevel::Warn);
1000
1001 assert!(filter.should_log("database", LogLevel::Debug));
1003 assert_eq!(filter.level_for("database"), LogLevel::Debug);
1004
1005 assert!(!filter.should_log("http", LogLevel::Info));
1007 assert!(filter.should_log("http", LogLevel::Warn));
1008 assert_eq!(filter.level_for("http"), LogLevel::Warn);
1009
1010 assert_eq!(filter.level_for("cache"), LogLevel::Info);
1012 assert!(filter.should_log("cache", LogLevel::Info));
1013 assert!(!filter.should_log("cache", LogLevel::Debug));
1014 }
1015
1016 #[test]
1017 fn test_level_filter_remove_target() {
1018 let mut filter = LevelFilter::new(LogLevel::Info).with_target_level("db", LogLevel::Debug);
1019 assert_eq!(filter.level_for("db"), LogLevel::Debug);
1020
1021 let removed = filter.remove_target("db");
1022 assert_eq!(removed, Some(LogLevel::Debug));
1023 assert_eq!(filter.level_for("db"), LogLevel::Info); }
1025
1026 #[test]
1027 fn test_level_filter_remove_missing_target_returns_none() {
1028 let mut filter = LevelFilter::new(LogLevel::Info);
1029 assert_eq!(filter.remove_target("never"), None);
1030 }
1031
1032 #[test]
1033 fn test_level_filter_set_default_level() {
1034 let mut filter = LevelFilter::new(LogLevel::Info);
1035 filter.set_default_level(LogLevel::Debug);
1036 assert_eq!(filter.default_level(), LogLevel::Debug);
1037 assert!(filter.should_log("any", LogLevel::Debug));
1038 }
1039
1040 #[test]
1041 fn test_level_filter_target_count_and_names() {
1042 let filter = LevelFilter::new(LogLevel::Info)
1043 .with_target_level("a", LogLevel::Debug)
1044 .with_target_level("b", LogLevel::Warn);
1045 assert_eq!(filter.target_count(), 2);
1046 let mut targets = filter.targets();
1047 targets.sort();
1048 assert_eq!(targets, vec!["a", "b"]);
1049 }
1050
1051 #[test]
1052 fn test_level_filter_default_impl() {
1053 let filter = LevelFilter::default();
1054 assert_eq!(filter.default_level(), LogLevel::Info);
1055 }
1056
1057 #[test]
1060 fn test_structured_log_entry_new() {
1061 let entry = StructuredLogEntry::new(LogLevel::Info, "hello");
1062 assert_eq!(entry.level, LogLevel::Info);
1063 assert_eq!(entry.message, "hello");
1064 assert!(entry.target.is_none());
1065 assert!(entry.fields.is_empty());
1066 assert!(!entry.timestamp.is_empty());
1067 }
1068
1069 #[test]
1070 fn test_structured_log_entry_with_target() {
1071 let entry = StructuredLogEntry::new(LogLevel::Info, "msg").with_target("database");
1072 assert_eq!(entry.target, Some("database".to_string()));
1073 }
1074
1075 #[test]
1076 fn test_structured_log_entry_with_field() {
1077 let entry = StructuredLogEntry::new(LogLevel::Info, "msg")
1078 .with_field("user_id", "12345")
1079 .with_field("action", "login");
1080 assert_eq!(entry.fields.get("user_id"), Some(&"12345".to_string()));
1081 assert_eq!(entry.fields.get("action"), Some(&"login".to_string()));
1082 assert_eq!(entry.fields.len(), 2);
1083 }
1084
1085 #[test]
1086 fn test_structured_log_entry_with_fields_batch() {
1087 let mut fields = StructuredFields::new();
1088 fields.insert("k1".to_string(), "v1".to_string());
1089 fields.insert("k2".to_string(), "v2".to_string());
1090 let entry = StructuredLogEntry::new(LogLevel::Info, "msg").with_fields(fields);
1091 assert_eq!(entry.fields.len(), 2);
1092 }
1093
1094 #[test]
1095 fn test_structured_log_entry_from_log_entry() {
1096 let original = LogEntry {
1097 level: LogLevel::Warn,
1098 message: "warning msg".to_string(),
1099 timestamp: "2024-01-01T00:00:00Z".to_string(),
1100 };
1101 let structured = StructuredLogEntry::from_log_entry(&original);
1102 assert_eq!(structured.level, LogLevel::Warn);
1103 assert_eq!(structured.message, "warning msg");
1104 assert_eq!(structured.timestamp, "2024-01-01T00:00:00Z");
1105 assert!(structured.target.is_none());
1106 assert!(structured.fields.is_empty());
1107 }
1108
1109 #[test]
1110 fn test_structured_log_entry_to_log_entry() {
1111 let structured = StructuredLogEntry::new(LogLevel::Error, "err")
1112 .with_target("db")
1113 .with_field("code", "500");
1114 let plain = structured.to_log_entry();
1115 assert_eq!(plain.level, LogLevel::Error);
1116 assert_eq!(plain.message, "err");
1117 }
1118
1119 #[test]
1120 fn test_structured_log_entry_json_roundtrip() {
1121 let entry = StructuredLogEntry::new(LogLevel::Info, "test")
1122 .with_target("app")
1123 .with_field("key", "value");
1124 let json = entry.to_json().expect("serialize");
1125 let back = StructuredLogEntry::from_json(&json).expect("deserialize");
1126 assert_eq!(back.level, entry.level);
1127 assert_eq!(back.message, entry.message);
1128 assert_eq!(back.target, entry.target);
1129 assert_eq!(back.fields, entry.fields);
1130 }
1131
1132 #[test]
1133 fn test_structured_log_entry_format_fields_sorted() {
1134 let entry = StructuredLogEntry::new(LogLevel::Info, "msg")
1135 .with_field("zebra", "1")
1136 .with_field("alpha", "2")
1137 .with_field("middle", "3");
1138 let formatted = entry.format_fields();
1139 assert_eq!(formatted, "alpha=2 middle=3 zebra=1");
1141 }
1142
1143 #[test]
1144 fn test_structured_log_entry_format_fields_empty() {
1145 let entry = StructuredLogEntry::new(LogLevel::Info, "msg");
1146 assert_eq!(entry.format_fields(), "");
1147 }
1148
1149 #[test]
1152 fn test_memory_structured_sink_new_empty() {
1153 let sink = MemoryStructuredSink::new("s");
1154 assert_eq!(sink.name(), "s");
1155 assert!(sink.is_empty());
1156 }
1157
1158 #[test]
1159 fn test_memory_structured_sink_stores_entry() {
1160 let sink = MemoryStructuredSink::new("s");
1161 let entry = StructuredLogEntry::new(LogLevel::Info, "hello").with_field("k", "v");
1162 sink.write(&entry);
1163 assert_eq!(sink.len(), 1);
1164 assert_eq!(sink.entries()[0].message, "hello");
1165 assert_eq!(sink.entries()[0].fields.get("k"), Some(&"v".to_string()));
1166 }
1167
1168 #[test]
1169 fn test_memory_structured_sink_clear() {
1170 let sink = MemoryStructuredSink::new("s");
1171 sink.write(&StructuredLogEntry::new(LogLevel::Info, "x"));
1172 sink.clear();
1173 assert!(sink.is_empty());
1174 }
1175
1176 #[test]
1179 fn test_structured_log_writer_filters_by_target() {
1180 let filter = LevelFilter::new(LogLevel::Info).with_target_level("verbose", LogLevel::Debug);
1181 let mut writer = StructuredLogWriter::new(filter);
1182 let sink = Arc::new(MemoryStructuredSink::new("mem"));
1183 writer.add_sink(sink.clone());
1184
1185 let debug_entry = StructuredLogEntry::new(LogLevel::Debug, "dbg").with_target("verbose");
1187 writer.log(&debug_entry);
1188 assert_eq!(sink.len(), 1);
1189
1190 let filtered = StructuredLogEntry::new(LogLevel::Debug, "filtered");
1192 writer.log(&filtered);
1193 assert_eq!(sink.len(), 1); }
1195
1196 #[test]
1197 fn test_structured_log_writer_fans_out_to_multiple_sinks() {
1198 let writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Debug));
1199 let sink1 = Arc::new(MemoryStructuredSink::new("s1"));
1200 let sink2 = Arc::new(MemoryStructuredSink::new("s2"));
1201
1202 let mut writer = writer;
1204 writer.add_sink(sink1.clone());
1205 writer.add_sink(sink2.clone());
1206
1207 let entry = StructuredLogEntry::new(LogLevel::Info, "hello");
1208 writer.log(&entry);
1209
1210 assert_eq!(sink1.len(), 1);
1211 assert_eq!(sink2.len(), 1);
1212 }
1213
1214 #[test]
1215 fn test_structured_log_writer_log_with_fields() {
1216 let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1217 let sink = Arc::new(MemoryStructuredSink::new("mem"));
1218 writer.add_sink(sink.clone());
1219
1220 let mut fields = StructuredFields::new();
1221 fields.insert("user_id".to_string(), "42".to_string());
1222 writer.log_with_fields("api", LogLevel::Info, "request", fields);
1223
1224 assert_eq!(sink.len(), 1);
1225 let entry = &sink.entries()[0];
1226 assert_eq!(entry.target, Some("api".to_string()));
1227 assert_eq!(entry.fields.get("user_id"), Some(&"42".to_string()));
1228 }
1229
1230 #[test]
1231 fn test_structured_log_writer_sink_names() {
1232 let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1233 writer.add_sink(Arc::new(MemoryStructuredSink::new("alpha")));
1234 writer.add_sink(Arc::new(MemoryStructuredSink::new("beta")));
1235 let names = writer.sink_names();
1236 assert_eq!(names, vec!["alpha", "beta"]);
1237 assert_eq!(writer.sink_count(), 2);
1238 }
1239
1240 #[test]
1241 fn test_structured_log_writer_filter_mut() {
1242 let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1243 writer.filter_mut().set_default_level(LogLevel::Error);
1244 assert_eq!(writer.filter().default_level(), LogLevel::Error);
1245 }
1246
1247 #[test]
1248 fn test_structured_log_writer_empty_sinks_no_error() {
1249 let writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Debug));
1250 let entry = StructuredLogEntry::new(LogLevel::Info, "msg");
1251 writer.log(&entry); assert_eq!(writer.sink_count(), 0);
1253 }
1254
1255 #[test]
1256 fn test_multi_output_logger_implements_send_sync() {
1257 fn assert_send_sync<T: Send + Sync>() {}
1258 assert_send_sync::<MultiOutputLogger>();
1259 assert_send_sync::<MemorySink>();
1260 assert_send_sync::<ConsoleSink>();
1261 }
1262}