Skip to main content

appcore_ops/
log.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: log.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/05/31 13:38:42 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/07/23 23:50:45 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11//! Logging contracts for runtime observability.
12
13use parking_lot::Mutex;
14
15/// Structured log level.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum LogLevel {
18    /// Fine-grained diagnostic event.
19    Trace,
20    /// Developer diagnostic event.
21    Debug,
22    /// Normal operational event.
23    Info,
24    /// Recoverable or degraded condition.
25    Warn,
26    /// Failed operation.
27    Error,
28}
29
30/// One runtime log record.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct LogRecord {
33    /// Record severity.
34    pub level: LogLevel,
35    /// Stable Runtime subsystem target.
36    pub target: String,
37    /// Non-sensitive message subject to redaction.
38    pub message: String,
39    /// Timestamp in Unix milliseconds.
40    pub timestamp_ms: u64,
41}
42
43/// Contract for runtime log sinks.
44pub trait RuntimeLogger: Send + Sync {
45    /// Emits one structured Runtime log record.
46    fn log(&self, record: LogRecord);
47}
48
49/// Minimal stdout logger for local runtime operation.
50#[derive(Debug, Default, Clone, Copy)]
51pub struct StdoutLogger;
52
53impl StdoutLogger {
54    /// Creates a stdout logger.
55    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/// In-memory logger for deterministic tests.
72#[derive(Debug, Default)]
73pub struct InMemoryLogger {
74    records: Mutex<Vec<LogRecord>>,
75}
76
77impl InMemoryLogger {
78    /// Creates an empty in-memory logger.
79    pub fn new() -> Self {
80        Self {
81            records: Mutex::new(Vec::new()),
82        }
83    }
84
85    /// Returns the number of retained records.
86    pub fn len(&self) -> usize {
87        self.records.lock().len()
88    }
89
90    /// Reports whether no records are retained.
91    pub fn is_empty(&self) -> bool {
92        self.records.lock().is_empty()
93    }
94
95    /// Returns a point-in-time copy of retained records.
96    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;