zerodds_security_logging/
fanout.rs1use alloc::boxed::Box;
12use alloc::vec::Vec;
13
14use zerodds_security::logging::{LogLevel, LoggingPlugin};
15
16pub struct FanOutLoggingPlugin {
20 sinks: Vec<Box<dyn LoggingPlugin>>,
21}
22
23impl Default for FanOutLoggingPlugin {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl FanOutLoggingPlugin {
30 #[must_use]
32 pub fn new() -> Self {
33 Self { sinks: Vec::new() }
34 }
35
36 #[must_use]
38 pub fn with<P: LoggingPlugin + 'static>(mut self, sink: P) -> Self {
39 self.sinks.push(Box::new(sink));
40 self
41 }
42
43 #[must_use]
46 pub fn with_boxed(mut self, sink: Box<dyn LoggingPlugin>) -> Self {
47 self.sinks.push(sink);
48 self
49 }
50
51 #[must_use]
53 pub fn sink_count(&self) -> usize {
54 self.sinks.len()
55 }
56}
57
58impl LoggingPlugin for FanOutLoggingPlugin {
59 fn log(&self, level: LogLevel, participant: [u8; 16], category: &str, message: &str) {
60 for sink in &self.sinks {
61 sink.log(level, participant, category, message);
62 }
63 }
64
65 fn plugin_class_id(&self) -> &str {
66 "DDS:Logging:fanout"
67 }
68}
69
70#[cfg(test)]
71#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
72mod tests {
73 use super::*;
74 use std::sync::{Arc, Mutex};
75 use zerodds_security::mock::{MockLogEntry, MockLogSink, MockLoggingPlugin};
76
77 fn sink() -> MockLogSink {
78 Arc::new(Mutex::new(Vec::<MockLogEntry>::new()))
79 }
80
81 #[test]
82 fn empty_fanout_drops_events_without_panic() {
83 let f = FanOutLoggingPlugin::new();
84 f.log(LogLevel::Error, [0u8; 16], "cat", "msg");
85 assert_eq!(f.sink_count(), 0);
86 }
87
88 #[test]
89 fn event_reaches_all_registered_sinks() {
90 let s1 = sink();
91 let s2 = sink();
92 let f = FanOutLoggingPlugin::new()
93 .with(MockLoggingPlugin::new(Arc::clone(&s1)))
94 .with(MockLoggingPlugin::new(Arc::clone(&s2)));
95 f.log(LogLevel::Error, [0xAA; 16], "auth.bad", "nope");
96 assert_eq!(s1.lock().unwrap().len(), 1);
97 assert_eq!(s2.lock().unwrap().len(), 1);
98 assert_eq!(s1.lock().unwrap()[0].category, "auth.bad");
99 }
100
101 #[test]
102 fn plugin_class_id_stable() {
103 assert_eq!(
104 FanOutLoggingPlugin::new().plugin_class_id(),
105 "DDS:Logging:fanout"
106 );
107 }
108}