zerodds_security_logging/
stderr_sink.rs1use alloc::string::String;
7use std::io::{self, Write};
8use std::sync::Mutex;
9
10use zerodds_security::logging::{LogLevel, LoggingPlugin};
11
12pub struct StderrLoggingPlugin {
24 min_level: LogLevel,
25 serializer: Mutex<()>,
26}
27
28impl Default for StderrLoggingPlugin {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl StderrLoggingPlugin {
35 #[must_use]
37 pub fn new() -> Self {
38 Self::with_level(LogLevel::Warning)
39 }
40
41 #[must_use]
43 pub fn with_level(min_level: LogLevel) -> Self {
44 Self {
45 min_level,
46 serializer: Mutex::new(()),
47 }
48 }
49}
50
51fn level_label(l: LogLevel) -> &'static str {
52 match l {
53 LogLevel::Emergency => "EMERG",
54 LogLevel::Alert => "ALERT",
55 LogLevel::Critical => "CRIT",
56 LogLevel::Error => "ERROR",
57 LogLevel::Warning => "WARN",
58 LogLevel::Notice => "NOTICE",
59 LogLevel::Informational => "INFO",
60 LogLevel::Debug => "DEBUG",
61 }
62}
63
64fn hex16(bytes: [u8; 16]) -> String {
65 let mut s = String::with_capacity(32);
66 for b in bytes {
67 s.push_str(&alloc::format!("{b:02x}"));
68 }
69 s
70}
71
72impl LoggingPlugin for StderrLoggingPlugin {
73 fn log(&self, level: LogLevel, participant: [u8; 16], category: &str, message: &str) {
74 if level > self.min_level {
76 return;
77 }
78 let _guard = self.serializer.lock().ok();
79 let mut out = io::stderr().lock();
80 let _ = writeln!(
81 out,
82 "[SEC][{level}] participant={pid} category={cat} msg={msg}",
83 level = level_label(level),
84 pid = hex16(participant),
85 cat = category,
86 msg = message,
87 );
88 }
89
90 fn plugin_class_id(&self) -> &str {
91 "DDS:Logging:stderr"
92 }
93}
94
95#[cfg(test)]
96#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn plugin_class_id_stable() {
102 assert_eq!(
103 StderrLoggingPlugin::new().plugin_class_id(),
104 "DDS:Logging:stderr"
105 );
106 }
107
108 #[test]
109 fn level_label_covers_all_variants() {
110 for &lvl in &[
111 LogLevel::Emergency,
112 LogLevel::Alert,
113 LogLevel::Critical,
114 LogLevel::Error,
115 LogLevel::Warning,
116 LogLevel::Notice,
117 LogLevel::Informational,
118 LogLevel::Debug,
119 ] {
120 assert!(!level_label(lvl).is_empty());
121 }
122 }
123
124 #[test]
125 fn hex16_pads_single_digits() {
126 let bytes = [0x01, 0x0a, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
127 assert_eq!(hex16(bytes), "010aff00000000000000000000000000");
128 }
129}