1use crate::{LogEntry, LogLevel, Logger};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::sync::Arc;
18use parking_lot::Mutex;
19use std::time::{Duration, Instant};
20
21#[derive(Debug, Clone)]
27pub enum RotationPolicy {
28 Size(u64),
30 Time(Duration),
32 SizeOrTime(u64, Duration),
34}
35
36impl RotationPolicy {
37 fn should_rotate(&self, current_size: u64, elapsed: Duration) -> bool {
43 match self {
44 RotationPolicy::Size(max_bytes) => current_size >= *max_bytes,
45 RotationPolicy::Time(max_age) => elapsed >= *max_age,
46 RotationPolicy::SizeOrTime(max_bytes, max_age) => {
47 current_size >= *max_bytes || elapsed >= *max_age
48 }
49 }
50 }
51}
52
53pub struct LogRotator {
62 policy: RotationPolicy,
64 max_files: usize,
66 current: Mutex<Vec<u8>>,
68 rotated: Mutex<Vec<Vec<u8>>>,
70 started_at: Mutex<Instant>,
72 rotation_count: Mutex<u64>,
74}
75
76impl LogRotator {
77 pub fn new(policy: RotationPolicy, max_files: usize) -> Self {
83 Self {
84 policy,
85 max_files,
86 current: Mutex::new(Vec::new()),
87 rotated: Mutex::new(Vec::new()),
88 started_at: Mutex::new(Instant::now()),
89 rotation_count: Mutex::new(0),
90 }
91 }
92
93 pub fn write(&self, data: &[u8]) {
95 let should_rotate = {
96 let mut current = self.current.lock();
97 current.extend_from_slice(data);
98 let started_at = self.started_at.lock();
99 self.policy
100 .should_rotate(current.len() as u64, started_at.elapsed())
101 };
102
103 if should_rotate {
104 self.rotate();
105 }
106 }
107
108 pub fn rotate(&self) {
110 let mut current = self.current.lock();
111 let mut rotated = self.rotated.lock();
112 let mut started_at = self.started_at.lock();
113 let mut count = self.rotation_count.lock();
114
115 let old_buffer = std::mem::take(&mut *current);
117 if !old_buffer.is_empty() {
118 rotated.insert(0, old_buffer);
119 }
120
121 while rotated.len() > self.max_files {
123 rotated.pop();
124 }
125
126 *started_at = Instant::now();
128 *count += 1;
129 }
130
131 pub fn current_size(&self) -> usize {
133 self.current.lock().len()
134 }
135
136 pub fn rotated_count(&self) -> usize {
138 self.rotated.lock().len()
139 }
140
141 pub fn total_rotations(&self) -> u64 {
143 *self.rotation_count.lock()
144 }
145
146 pub fn current_content(&self) -> Vec<u8> {
148 self.current.lock().clone()
149 }
150
151 pub fn rotated_content(&self, index: usize) -> Option<Vec<u8>> {
153 let rotated = self.rotated.lock();
154 rotated.get(index).cloned()
155 }
156
157 pub fn current_age(&self) -> Duration {
159 self.started_at.lock().elapsed()
160 }
161
162 pub fn policy(&self) -> &RotationPolicy {
164 &self.policy
165 }
166
167 pub fn max_files(&self) -> usize {
169 self.max_files
170 }
171
172 pub fn clear(&self) {
174 self.current.lock().clear();
175 self.rotated.lock().clear();
176 *self.started_at.lock() = Instant::now();
177 }
178}
179
180pub trait LogSink: Send + Sync {
189 fn write(&self, entry: &LogEntry);
191 fn name(&self) -> &str;
193}
194
195pub struct MemorySink {
197 name: String,
198 entries: Mutex<Vec<LogEntry>>,
199}
200
201impl MemorySink {
202 pub fn new(name: impl Into<String>) -> Self {
203 Self {
204 name: name.into(),
205 entries: Mutex::new(Vec::new()),
206 }
207 }
208
209 pub fn entries(&self) -> Vec<LogEntry> {
211 self.entries.lock().clone()
212 }
213
214 pub fn len(&self) -> usize {
216 self.entries.lock().len()
217 }
218
219 pub fn is_empty(&self) -> bool {
221 self.entries.lock().is_empty()
222 }
223
224 pub fn clear(&self) {
226 self.entries.lock().clear();
227 }
228}
229
230impl LogSink for MemorySink {
231 fn write(&self, entry: &LogEntry) {
232 let mut entries = self.entries.lock();
234 entries.push(entry.clone());
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().clone()
597 }
598
599 pub fn len(&self) -> usize {
600 self.entries.lock().len()
601 }
602
603 pub fn is_empty(&self) -> bool {
604 self.entries.lock().is_empty()
605 }
606
607 pub fn clear(&self) {
608 self.entries.lock().clear();
609 }
610}
611
612impl StructuredSink for MemoryStructuredSink {
613 fn write(&self, entry: &StructuredLogEntry) {
614 let mut entries = self.entries.lock();
615 entries.push(entry.clone());
616 }
617
618 fn name(&self) -> &str {
619 &self.name
620 }
621}
622
623impl StructuredLogWriter {
624 pub fn new(filter: LevelFilter) -> Self {
626 Self {
627 filter,
628 sinks: Vec::new(),
629 }
630 }
631
632 pub fn add_sink(&mut self, sink: Arc<dyn StructuredSink>) -> &mut Self {
634 self.sinks.push(sink);
635 self
636 }
637
638 pub fn sink_count(&self) -> usize {
640 self.sinks.len()
641 }
642
643 pub fn sink_names(&self) -> Vec<String> {
645 self.sinks.iter().map(|s| s.name().to_string()).collect()
646 }
647
648 pub fn filter(&self) -> &LevelFilter {
650 &self.filter
651 }
652
653 pub fn filter_mut(&mut self) -> &mut LevelFilter {
655 &mut self.filter
656 }
657
658 pub fn log(&self, entry: &StructuredLogEntry) {
663 let target = entry.target.as_deref().unwrap_or("");
664 if !self.filter.should_log(target, entry.level) {
665 return;
666 }
667 for sink in &self.sinks {
668 sink.write(entry);
669 }
670 }
671
672 pub fn log_with_fields(
674 &self,
675 target: impl Into<String>,
676 level: LogLevel,
677 message: impl Into<String>,
678 fields: StructuredFields,
679 ) {
680 let entry = StructuredLogEntry::new(level, message)
681 .with_target(target)
682 .with_fields(fields);
683 self.log(&entry);
684 }
685}
686
687impl StructuredLogEntry {
689 pub fn with_fields(mut self, fields: StructuredFields) -> Self {
691 self.fields.extend(fields);
692 self
693 }
694}
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699 use std::thread;
700 use std::time::Duration;
701
702 #[test]
705 fn test_rotation_policy_size_met() {
706 let policy = RotationPolicy::Size(100);
707 assert!(policy.should_rotate(100, Duration::from_secs(0)));
708 assert!(policy.should_rotate(101, Duration::from_secs(0)));
709 assert!(!policy.should_rotate(99, Duration::from_secs(0)));
710 }
711
712 #[test]
713 fn test_rotation_policy_time_met() {
714 let policy = RotationPolicy::Time(Duration::from_secs(60));
715 assert!(policy.should_rotate(0, Duration::from_secs(60)));
716 assert!(policy.should_rotate(0, Duration::from_secs(61)));
717 assert!(!policy.should_rotate(0, Duration::from_secs(59)));
718 }
719
720 #[test]
721 fn test_rotation_policy_size_or_time_either() {
722 let policy = RotationPolicy::SizeOrTime(100, Duration::from_secs(60));
723 assert!(policy.should_rotate(100, Duration::from_secs(0)));
725 assert!(policy.should_rotate(0, Duration::from_secs(60)));
727 assert!(!policy.should_rotate(99, Duration::from_secs(59)));
729 assert!(policy.should_rotate(200, Duration::from_secs(120)));
731 }
732
733 #[test]
736 fn test_log_rotator_new_empty() {
737 let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
738 assert_eq!(rotator.current_size(), 0);
739 assert_eq!(rotator.rotated_count(), 0);
740 assert_eq!(rotator.total_rotations(), 0);
741 assert_eq!(rotator.max_files(), 3);
742 }
743
744 #[test]
745 fn test_log_rotator_write_accumulates() {
746 let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
747 rotator.write(b"hello");
748 rotator.write(b" world");
749 assert_eq!(rotator.current_size(), 11);
750 assert_eq!(rotator.current_content(), b"hello world");
751 }
752
753 #[test]
754 fn test_log_rotator_size_triggers_rotation() {
755 let rotator = LogRotator::new(RotationPolicy::Size(10), 3);
756 rotator.write(b"12345"); assert_eq!(rotator.rotated_count(), 0);
758 rotator.write(b"67890"); assert_eq!(rotator.rotated_count(), 1);
760 assert_eq!(rotator.total_rotations(), 1);
761 assert_eq!(rotator.current_size(), 0);
762 }
763
764 #[test]
765 fn test_log_rotator_rotation_preserves_content() {
766 let rotator = LogRotator::new(RotationPolicy::Size(10), 3);
767 rotator.write(b"hello world"); assert_eq!(rotator.rotated_count(), 1);
769 let rotated = rotator.rotated_content(0).expect("rotated[0] must exist");
770 assert_eq!(rotated, b"hello world");
771 }
772
773 #[test]
774 fn test_log_rotator_max_files_drops_oldest() {
775 let rotator = LogRotator::new(RotationPolicy::Size(5), 2);
776 rotator.write(b"AAAAAA"); rotator.write(b"BBBBBB"); rotator.write(b"CCCCCC"); assert_eq!(rotator.rotated_count(), 2);
780 assert_eq!(rotator.total_rotations(), 3);
781 assert_eq!(rotator.rotated_content(0), Some(b"CCCCCC".to_vec()));
783 assert_eq!(rotator.rotated_content(1), Some(b"BBBBBB".to_vec()));
784 assert_eq!(rotator.rotated_content(2), None);
785 }
786
787 #[test]
788 fn test_log_rotator_manual_rotate() {
789 let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
790 rotator.write(b"some data");
791 rotator.rotate();
792 assert_eq!(rotator.rotated_count(), 1);
793 assert_eq!(rotator.current_size(), 0);
794 }
795
796 #[test]
797 fn test_log_rotator_manual_rotate_empty_no_op() {
798 let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
799 rotator.rotate(); assert_eq!(rotator.rotated_count(), 0);
801 assert_eq!(rotator.total_rotations(), 1); }
803
804 #[test]
805 fn test_log_rotator_clear() {
806 let rotator = LogRotator::new(RotationPolicy::Size(5), 3);
807 rotator.write(b"hello world"); rotator.write(b"more");
809 assert!(!rotator.current_content().is_empty());
810 assert_eq!(rotator.rotated_count(), 1);
811
812 rotator.clear();
813 assert_eq!(rotator.current_size(), 0);
814 assert_eq!(rotator.rotated_count(), 0);
815 }
816
817 #[test]
818 fn test_log_rotator_policy_accessor() {
819 let rotator = LogRotator::new(RotationPolicy::Size(256), 5);
820 match rotator.policy() {
821 RotationPolicy::Size(n) => assert_eq!(*n, 256),
822 _ => panic!("expected Size policy"),
823 }
824 }
825
826 #[test]
827 fn test_log_rotator_time_based_rotation() {
828 let rotator = LogRotator::new(RotationPolicy::Time(Duration::from_millis(50)), 3);
829 rotator.write(b"data");
830 assert_eq!(rotator.rotated_count(), 0);
831 thread::sleep(Duration::from_millis(60));
832 rotator.write(b"more"); assert_eq!(rotator.rotated_count(), 1);
834 }
835
836 #[test]
837 fn test_log_rotator_current_age_increases() {
838 let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
839 let age1 = rotator.current_age();
840 thread::sleep(Duration::from_millis(10));
841 let age2 = rotator.current_age();
842 assert!(age2 > age1);
843 }
844
845 #[test]
848 fn test_memory_sink_new_empty() {
849 let sink = MemorySink::new("test");
850 assert_eq!(sink.name(), "test");
851 assert!(sink.is_empty());
852 assert_eq!(sink.len(), 0);
853 }
854
855 #[test]
856 fn test_memory_sink_write_stores_entry() {
857 let sink = MemorySink::new("mem");
858 let entry = LogEntry {
859 level: LogLevel::Info,
860 message: "hello".to_string(),
861 timestamp: "2024-01-01T00:00:00Z".to_string(),
862 };
863 sink.write(&entry);
864 assert_eq!(sink.len(), 1);
865 assert_eq!(sink.entries()[0].message, "hello");
866 }
867
868 #[test]
869 fn test_memory_sink_clear() {
870 let sink = MemorySink::new("mem");
871 let entry = LogEntry {
872 level: LogLevel::Info,
873 message: "x".to_string(),
874 timestamp: "t".to_string(),
875 };
876 sink.write(&entry);
877 sink.write(&entry);
878 assert_eq!(sink.len(), 2);
879 sink.clear();
880 assert!(sink.is_empty());
881 }
882
883 #[test]
886 fn test_console_sink_name_default() {
887 let sink = ConsoleSink::new();
888 assert_eq!(sink.name(), "console");
889 }
890
891 #[test]
892 fn test_console_sink_custom_name() {
893 let sink = ConsoleSink::with_name("stdout");
894 assert_eq!(sink.name(), "stdout");
895 }
896
897 #[test]
898 fn test_console_sink_write_does_not_panic() {
899 let sink = ConsoleSink::new();
900 let entry = LogEntry {
901 level: LogLevel::Info,
902 message: "test".to_string(),
903 timestamp: "t".to_string(),
904 };
905 sink.write(&entry); }
907
908 #[test]
911 fn test_callback_sink_invokes_closure() {
912 let counter = Arc::new(Mutex::new(0u32));
913 let c = counter.clone();
914 let sink = CallbackSink::new("cb", move |_entry| {
915 *c.lock() += 1;
916 });
917 let entry = LogEntry {
918 level: LogLevel::Info,
919 message: "x".to_string(),
920 timestamp: "t".to_string(),
921 };
922 sink.write(&entry);
923 sink.write(&entry);
924 assert_eq!(*counter.lock(), 2);
925 }
926
927 #[test]
930 fn test_multi_output_logger_fans_out_to_all_sinks() {
931 let mut logger = MultiOutputLogger::new(LogLevel::Debug);
932 let sink1 = Arc::new(MemorySink::new("s1"));
933 let sink2 = Arc::new(MemorySink::new("s2"));
934
935 logger.add_sink(sink1.clone());
936 logger.add_sink(sink2.clone());
937
938 logger.log(LogLevel::Info, "hello");
939
940 assert_eq!(sink1.len(), 1);
941 assert_eq!(sink2.len(), 1);
942 assert_eq!(sink1.entries()[0].message, "hello");
943 assert_eq!(sink2.entries()[0].message, "hello");
944 }
945
946 #[test]
947 fn test_multi_output_logger_respects_level_filter() {
948 let mut logger = MultiOutputLogger::new(LogLevel::Warn);
949 let sink = Arc::new(MemorySink::new("s"));
950 logger.add_sink(sink.clone());
951
952 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);
958 }
959
960 #[test]
961 fn test_multi_output_logger_sink_names() {
962 let mut logger = MultiOutputLogger::new(LogLevel::Info);
963 logger.add_sink(Arc::new(MemorySink::new("alpha")));
964 logger.add_sink(Arc::new(MemorySink::new("beta")));
965 let names = logger.sink_names();
966 assert_eq!(names, vec!["alpha", "beta"]);
967 assert_eq!(logger.sink_count(), 2);
968 }
969
970 #[test]
971 fn test_multi_output_logger_empty_sinks_no_error() {
972 let logger = MultiOutputLogger::new(LogLevel::Info);
973 logger.log(LogLevel::Info, "msg"); assert_eq!(logger.sink_count(), 0);
975 }
976
977 #[test]
978 fn test_multi_output_logger_level_accessor() {
979 let logger = MultiOutputLogger::new(LogLevel::Error);
980 assert_eq!(logger.level(), LogLevel::Error);
981 }
982
983 #[test]
986 fn test_level_filter_default_level() {
987 let filter = LevelFilter::new(LogLevel::Info);
988 assert_eq!(filter.default_level(), LogLevel::Info);
989 assert_eq!(filter.level_for("anything"), LogLevel::Info);
990 assert!(filter.should_log("anything", LogLevel::Info));
991 assert!(!filter.should_log("anything", LogLevel::Debug));
992 }
993
994 #[test]
995 fn test_level_filter_target_override() {
996 let filter = LevelFilter::new(LogLevel::Info)
997 .with_target_level("database", LogLevel::Debug)
998 .with_target_level("http", LogLevel::Warn);
999
1000 assert!(filter.should_log("database", LogLevel::Debug));
1002 assert_eq!(filter.level_for("database"), LogLevel::Debug);
1003
1004 assert!(!filter.should_log("http", LogLevel::Info));
1006 assert!(filter.should_log("http", LogLevel::Warn));
1007 assert_eq!(filter.level_for("http"), LogLevel::Warn);
1008
1009 assert_eq!(filter.level_for("cache"), LogLevel::Info);
1011 assert!(filter.should_log("cache", LogLevel::Info));
1012 assert!(!filter.should_log("cache", LogLevel::Debug));
1013 }
1014
1015 #[test]
1016 fn test_level_filter_remove_target() {
1017 let mut filter = LevelFilter::new(LogLevel::Info).with_target_level("db", LogLevel::Debug);
1018 assert_eq!(filter.level_for("db"), LogLevel::Debug);
1019
1020 let removed = filter.remove_target("db");
1021 assert_eq!(removed, Some(LogLevel::Debug));
1022 assert_eq!(filter.level_for("db"), LogLevel::Info); }
1024
1025 #[test]
1026 fn test_level_filter_remove_missing_target_returns_none() {
1027 let mut filter = LevelFilter::new(LogLevel::Info);
1028 assert_eq!(filter.remove_target("never"), None);
1029 }
1030
1031 #[test]
1032 fn test_level_filter_set_default_level() {
1033 let mut filter = LevelFilter::new(LogLevel::Info);
1034 filter.set_default_level(LogLevel::Debug);
1035 assert_eq!(filter.default_level(), LogLevel::Debug);
1036 assert!(filter.should_log("any", LogLevel::Debug));
1037 }
1038
1039 #[test]
1040 fn test_level_filter_target_count_and_names() {
1041 let filter = LevelFilter::new(LogLevel::Info)
1042 .with_target_level("a", LogLevel::Debug)
1043 .with_target_level("b", LogLevel::Warn);
1044 assert_eq!(filter.target_count(), 2);
1045 let mut targets = filter.targets();
1046 targets.sort();
1047 assert_eq!(targets, vec!["a", "b"]);
1048 }
1049
1050 #[test]
1051 fn test_level_filter_default_impl() {
1052 let filter = LevelFilter::default();
1053 assert_eq!(filter.default_level(), LogLevel::Info);
1054 }
1055
1056 #[test]
1059 fn test_structured_log_entry_new() {
1060 let entry = StructuredLogEntry::new(LogLevel::Info, "hello");
1061 assert_eq!(entry.level, LogLevel::Info);
1062 assert_eq!(entry.message, "hello");
1063 assert!(entry.target.is_none());
1064 assert!(entry.fields.is_empty());
1065 assert!(!entry.timestamp.is_empty());
1066 }
1067
1068 #[test]
1069 fn test_structured_log_entry_with_target() {
1070 let entry = StructuredLogEntry::new(LogLevel::Info, "msg").with_target("database");
1071 assert_eq!(entry.target, Some("database".to_string()));
1072 }
1073
1074 #[test]
1075 fn test_structured_log_entry_with_field() {
1076 let entry = StructuredLogEntry::new(LogLevel::Info, "msg")
1077 .with_field("user_id", "12345")
1078 .with_field("action", "login");
1079 assert_eq!(entry.fields.get("user_id"), Some(&"12345".to_string()));
1080 assert_eq!(entry.fields.get("action"), Some(&"login".to_string()));
1081 assert_eq!(entry.fields.len(), 2);
1082 }
1083
1084 #[test]
1085 fn test_structured_log_entry_with_fields_batch() {
1086 let mut fields = StructuredFields::new();
1087 fields.insert("k1".to_string(), "v1".to_string());
1088 fields.insert("k2".to_string(), "v2".to_string());
1089 let entry = StructuredLogEntry::new(LogLevel::Info, "msg").with_fields(fields);
1090 assert_eq!(entry.fields.len(), 2);
1091 }
1092
1093 #[test]
1094 fn test_structured_log_entry_from_log_entry() {
1095 let original = LogEntry {
1096 level: LogLevel::Warn,
1097 message: "warning msg".to_string(),
1098 timestamp: "2024-01-01T00:00:00Z".to_string(),
1099 };
1100 let structured = StructuredLogEntry::from_log_entry(&original);
1101 assert_eq!(structured.level, LogLevel::Warn);
1102 assert_eq!(structured.message, "warning msg");
1103 assert_eq!(structured.timestamp, "2024-01-01T00:00:00Z");
1104 assert!(structured.target.is_none());
1105 assert!(structured.fields.is_empty());
1106 }
1107
1108 #[test]
1109 fn test_structured_log_entry_to_log_entry() {
1110 let structured = StructuredLogEntry::new(LogLevel::Error, "err")
1111 .with_target("db")
1112 .with_field("code", "500");
1113 let plain = structured.to_log_entry();
1114 assert_eq!(plain.level, LogLevel::Error);
1115 assert_eq!(plain.message, "err");
1116 }
1117
1118 #[test]
1119 fn test_structured_log_entry_json_roundtrip() {
1120 let entry = StructuredLogEntry::new(LogLevel::Info, "test")
1121 .with_target("app")
1122 .with_field("key", "value");
1123 let json = entry.to_json().expect("serialize");
1124 let back = StructuredLogEntry::from_json(&json).expect("deserialize");
1125 assert_eq!(back.level, entry.level);
1126 assert_eq!(back.message, entry.message);
1127 assert_eq!(back.target, entry.target);
1128 assert_eq!(back.fields, entry.fields);
1129 }
1130
1131 #[test]
1132 fn test_structured_log_entry_format_fields_sorted() {
1133 let entry = StructuredLogEntry::new(LogLevel::Info, "msg")
1134 .with_field("zebra", "1")
1135 .with_field("alpha", "2")
1136 .with_field("middle", "3");
1137 let formatted = entry.format_fields();
1138 assert_eq!(formatted, "alpha=2 middle=3 zebra=1");
1140 }
1141
1142 #[test]
1143 fn test_structured_log_entry_format_fields_empty() {
1144 let entry = StructuredLogEntry::new(LogLevel::Info, "msg");
1145 assert_eq!(entry.format_fields(), "");
1146 }
1147
1148 #[test]
1151 fn test_memory_structured_sink_new_empty() {
1152 let sink = MemoryStructuredSink::new("s");
1153 assert_eq!(sink.name(), "s");
1154 assert!(sink.is_empty());
1155 }
1156
1157 #[test]
1158 fn test_memory_structured_sink_stores_entry() {
1159 let sink = MemoryStructuredSink::new("s");
1160 let entry = StructuredLogEntry::new(LogLevel::Info, "hello").with_field("k", "v");
1161 sink.write(&entry);
1162 assert_eq!(sink.len(), 1);
1163 assert_eq!(sink.entries()[0].message, "hello");
1164 assert_eq!(sink.entries()[0].fields.get("k"), Some(&"v".to_string()));
1165 }
1166
1167 #[test]
1168 fn test_memory_structured_sink_clear() {
1169 let sink = MemoryStructuredSink::new("s");
1170 sink.write(&StructuredLogEntry::new(LogLevel::Info, "x"));
1171 sink.clear();
1172 assert!(sink.is_empty());
1173 }
1174
1175 #[test]
1178 fn test_structured_log_writer_filters_by_target() {
1179 let filter = LevelFilter::new(LogLevel::Info).with_target_level("verbose", LogLevel::Debug);
1180 let mut writer = StructuredLogWriter::new(filter);
1181 let sink = Arc::new(MemoryStructuredSink::new("mem"));
1182 writer.add_sink(sink.clone());
1183
1184 let debug_entry = StructuredLogEntry::new(LogLevel::Debug, "dbg").with_target("verbose");
1186 writer.log(&debug_entry);
1187 assert_eq!(sink.len(), 1);
1188
1189 let filtered = StructuredLogEntry::new(LogLevel::Debug, "filtered");
1191 writer.log(&filtered);
1192 assert_eq!(sink.len(), 1); }
1194
1195 #[test]
1196 fn test_structured_log_writer_fans_out_to_multiple_sinks() {
1197 let writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Debug));
1198 let sink1 = Arc::new(MemoryStructuredSink::new("s1"));
1199 let sink2 = Arc::new(MemoryStructuredSink::new("s2"));
1200
1201 let mut writer = writer;
1203 writer.add_sink(sink1.clone());
1204 writer.add_sink(sink2.clone());
1205
1206 let entry = StructuredLogEntry::new(LogLevel::Info, "hello");
1207 writer.log(&entry);
1208
1209 assert_eq!(sink1.len(), 1);
1210 assert_eq!(sink2.len(), 1);
1211 }
1212
1213 #[test]
1214 fn test_structured_log_writer_log_with_fields() {
1215 let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1216 let sink = Arc::new(MemoryStructuredSink::new("mem"));
1217 writer.add_sink(sink.clone());
1218
1219 let mut fields = StructuredFields::new();
1220 fields.insert("user_id".to_string(), "42".to_string());
1221 writer.log_with_fields("api", LogLevel::Info, "request", fields);
1222
1223 assert_eq!(sink.len(), 1);
1224 let entry = &sink.entries()[0];
1225 assert_eq!(entry.target, Some("api".to_string()));
1226 assert_eq!(entry.fields.get("user_id"), Some(&"42".to_string()));
1227 }
1228
1229 #[test]
1230 fn test_structured_log_writer_sink_names() {
1231 let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1232 writer.add_sink(Arc::new(MemoryStructuredSink::new("alpha")));
1233 writer.add_sink(Arc::new(MemoryStructuredSink::new("beta")));
1234 let names = writer.sink_names();
1235 assert_eq!(names, vec!["alpha", "beta"]);
1236 assert_eq!(writer.sink_count(), 2);
1237 }
1238
1239 #[test]
1240 fn test_structured_log_writer_filter_mut() {
1241 let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1242 writer.filter_mut().set_default_level(LogLevel::Error);
1243 assert_eq!(writer.filter().default_level(), LogLevel::Error);
1244 }
1245
1246 #[test]
1247 fn test_structured_log_writer_empty_sinks_no_error() {
1248 let writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Debug));
1249 let entry = StructuredLogEntry::new(LogLevel::Info, "msg");
1250 writer.log(&entry); assert_eq!(writer.sink_count(), 0);
1252 }
1253
1254 #[test]
1255 fn test_multi_output_logger_implements_send_sync() {
1256 fn assert_send_sync<T: Send + Sync>() {}
1257 assert_send_sync::<MultiOutputLogger>();
1258 assert_send_sync::<MemorySink>();
1259 assert_send_sync::<ConsoleSink>();
1260 }
1261}