1use parking_lot::Mutex;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum LogLevel {
18 Trace,
20 Debug,
22 Info,
24 Warn,
26 Error,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct LogRecord {
33 pub level: LogLevel,
35 pub target: String,
37 pub message: String,
39 pub timestamp_ms: u64,
41}
42
43pub trait RuntimeLogger: Send + Sync {
45 fn log(&self, record: LogRecord);
47}
48
49#[derive(Debug, Default, Clone, Copy)]
51pub struct StdoutLogger;
52
53impl StdoutLogger {
54 pub fn new() -> Self {
56 Self
57 }
58}
59
60impl RuntimeLogger for StdoutLogger {
61 fn log(&self, record: LogRecord) {
62 let message = appcore_core::redact_text(&record.message);
63 let target = appcore_core::redact_text_with_limit(&record.target, 128);
64 println!(
65 "[{:?}] {} {} {}",
66 record.level, target, message, record.timestamp_ms
67 );
68 }
69}
70
71#[derive(Debug, Default)]
73pub struct InMemoryLogger {
74 records: Mutex<Vec<LogRecord>>,
75}
76
77impl InMemoryLogger {
78 pub fn new() -> Self {
80 Self {
81 records: Mutex::new(Vec::new()),
82 }
83 }
84
85 pub fn len(&self) -> usize {
87 self.records.lock().len()
88 }
89
90 pub fn is_empty(&self) -> bool {
92 self.records.lock().is_empty()
93 }
94
95 pub fn records(&self) -> Vec<LogRecord> {
97 self.records.lock().clone()
98 }
99}
100
101impl RuntimeLogger for InMemoryLogger {
102 fn log(&self, record: LogRecord) {
103 let mut record = record;
104 record.message = appcore_core::redact_text(&record.message);
105 record.target = appcore_core::redact_text_with_limit(&record.target, 128);
106 self.records.lock().push(record);
107 }
108}
109
110#[cfg(test)]
111#[path = "log_tests.rs"]
112mod tests;