Skip to main content

appcore_log/
config.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: config.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: working-tree by dnettoRaw
7//    ##   ## ##   ##    U: working-tree by dnettoRaw
8//      ###########      S: 1.0.2-rc
9// =============================================================================
10
11//! High-level destination selection without global logging state.
12
13use crate::{
14    ConsoleSink, FileSink, FileSinkConfig, LogDispatcher, LogError, LogPolicy, LogSink,
15    RingBufferSink,
16};
17use std::sync::Arc;
18
19/// Explicit destinations for ordinary operational logs.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum LogOutputMode {
22    /// Perform no logging work after the cheap enabled check.
23    Disabled,
24    /// Write human-readable output to the terminal.
25    Terminal,
26    /// Write bounded structured JSONL files.
27    File,
28    /// Write to both terminal and bounded JSONL files.
29    TerminalAndFile,
30    /// Retain a bounded sanitized ring and write it only on explicit crash dump.
31    CrashOnly,
32}
33
34/// Invalid high-level logger configuration.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum LogConfigError {
37    /// The selected mode requires a file configuration.
38    MissingFile,
39    /// A sink rejected its bounded configuration.
40    Sink(LogError),
41}
42
43impl From<LogError> for LogConfigError {
44    fn from(error: LogError) -> Self {
45        Self::Sink(error)
46    }
47}
48
49/// Complete explicit logger configuration.
50#[derive(Debug, Clone)]
51pub struct LoggerConfig {
52    /// Filtering and sanitization policy.
53    pub policy: LogPolicy,
54    /// Terminal, file, both, disabled or crash-only behavior.
55    pub output: LogOutputMode,
56    /// File configuration used by file output or an explicit crash dump.
57    pub file: Option<FileSinkConfig>,
58    /// Maximum events retained by crash-only mode.
59    pub crash_events: usize,
60    /// Maximum estimated bytes retained by crash-only mode.
61    pub crash_bytes: usize,
62}
63
64impl Default for LoggerConfig {
65    fn default() -> Self {
66        Self {
67            policy: LogPolicy::default(),
68            output: LogOutputMode::Terminal,
69            file: None,
70            crash_events: 256,
71            crash_bytes: 1024 * 1024,
72        }
73    }
74}
75
76impl LoggerConfig {
77    /// Builds a logger with no hidden global state or background thread.
78    pub fn build(self) -> Result<ConfiguredLogger, LogConfigError> {
79        let Self {
80            policy,
81            output,
82            file,
83            crash_events,
84            crash_bytes,
85        } = self;
86        let mut sinks = Vec::<Arc<dyn LogSink>>::with_capacity(2);
87        let mut crash_ring = None;
88        let mut crash_sink = None;
89
90        if matches!(
91            output,
92            LogOutputMode::Terminal | LogOutputMode::TerminalAndFile
93        ) {
94            sinks.push(Arc::new(ConsoleSink::new()));
95        }
96        if matches!(output, LogOutputMode::File | LogOutputMode::TerminalAndFile) {
97            let file = file.ok_or(LogConfigError::MissingFile)?;
98            sinks.push(Arc::new(FileSink::new(file)?));
99        } else if output == LogOutputMode::CrashOnly {
100            let file = file.ok_or(LogConfigError::MissingFile)?;
101            let ring = Arc::new(RingBufferSink::new(crash_events, crash_bytes)?);
102            sinks.push(ring.clone());
103            crash_ring = Some(ring);
104            crash_sink = Some(FileSink::new(file)?);
105        }
106
107        Ok(ConfiguredLogger {
108            dispatcher: LogDispatcher::new(policy, sinks),
109            crash_ring,
110            crash_sink,
111        })
112    }
113}
114
115/// Logger assembled from [`LoggerConfig`], including optional crash retention.
116pub struct ConfiguredLogger {
117    dispatcher: LogDispatcher,
118    crash_ring: Option<Arc<RingBufferSink>>,
119    crash_sink: Option<FileSink>,
120}
121
122impl ConfiguredLogger {
123    /// Returns the dispatcher used by application and Runtime components.
124    pub fn dispatcher(&self) -> &LogDispatcher {
125        &self.dispatcher
126    }
127
128    /// Writes the sanitized crash ring to its configured bounded file.
129    pub fn dump_crash(&self) -> Result<usize, LogConfigError> {
130        let Some(ring) = &self.crash_ring else {
131            return Ok(0);
132        };
133        let sink = self
134            .crash_sink
135            .as_ref()
136            .ok_or(LogConfigError::MissingFile)?;
137        let events = ring.snapshot();
138        for event in &events {
139            sink.emit(event)?;
140        }
141        Ok(events.len())
142    }
143}