timber_rust/service/write/
std.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Dante Doménech Martinez dante19031999@gmail.com
3
4use crate::service::ServiceError;
5use crate::service::fallback::Fallback;
6use crate::service::write::{StandardMessageFormatter, MessageFormatter};
7use crate::{LoggerStatus, Message, Service};
8use std::any::Any;
9use std::sync::Mutex;
10// =======================================================================
11// Cout / Cerr Services
12// =======================================================================
13
14/// A logging [`Service`] that targets the standard output stream ([`std::io::stdout`]).
15///
16/// Unlike other [`Service`]s, [`Cout`] service does not own its writer. Instead, it
17/// acquires a handle to the global process `stdout` during every [`work`](Self::work) call.
18///
19/// ### Why the Mutex?
20/// Even though `stdout` is globally available, the [`MessageFormatter`] (field `formatter`)
21/// may hold internal state (like line counters or timing data) that is not thread-safe.
22/// Wrapping the formatter in a [`Mutex`] ensures that the formatting logic remains
23/// atomic and synchronized across threads.
24pub struct Cout<F>
25where
26    F: MessageFormatter,
27{
28    /// Thread-safe access to the formatting logic.
29    formatter: Mutex<F>,
30}
31
32impl<F> Cout<F>
33where
34    F: MessageFormatter,
35{
36    /// Creates a new [`Cout`] service on the heap.
37    pub fn new() -> Box<Self> {
38        Box::new(Self {
39            formatter: Mutex::new(Default::default()),
40        })
41    }
42
43    /// Creates a new [`Cout`] service on the heap  with a custom [formatter][`MessageFormatter`].
44    pub fn with_formatter(formatter: F) -> Box<Self> {
45        Box::new(Self {
46            formatter: Mutex::new(formatter),
47        })
48    }
49}
50
51impl<F> Service for Cout<F>
52where
53    F: MessageFormatter + 'static,
54{
55    fn status(&self) -> LoggerStatus {
56        LoggerStatus::Running
57    }
58
59    /// Acquires the formatter lock and streams the formatted message to `stdout`.
60    ///
61    /// This method locks the global `stdout` stream for the duration of the formatting
62    /// process. This prevents "line interleaving" where parts of different log
63    /// messages appear mixed in the console.
64    ///
65    /// # Errors
66    /// Returns [`ServiceError::LockPoisoned`] if the internal formatter mutex is poisoned.
67    fn work(&self, msg: &Message) -> Result<(), ServiceError> {
68        let mut formatter_guard = self.formatter.lock()?;
69        let mut out = std::io::stdout();
70        formatter_guard.format_io(msg, &mut out)?;
71        Ok(())
72    }
73
74    fn as_any(&self) -> &dyn Any {
75        self
76    }
77}
78
79impl<F> Fallback for Cout<F>
80where
81    F: MessageFormatter + 'static,
82{
83    /// Emergency fallback that attempts to log the error back to `stdout`.
84    /// If the formatter is locked or poisoned, the fallback is aborted to avoid deadlocks.
85    fn fallback(&self, error: &ServiceError, msg: &Message) {
86        if let Ok(mut guard) = self.formatter.lock() {
87            let mut out = std::io::stdout();
88            let _ = guard.format_io(msg, &mut out);
89            let _ = eprintln!("CoutWriteService Error: {}", error);
90        }
91    }
92}
93
94/// A [`Cout`] service pre-configured with the [`StandardMessageFormatter`].
95pub type StandardCout = Cout<StandardMessageFormatter>;
96
97/// A logging [`Service`] that targets the standard error stream ([`std::io::stderr`]).
98///
99/// [`Cerr`] service is typically used for high-priority alerts or diagnostic
100/// information that should remain visible even if `stdout` is redirected to a file.
101pub struct Cerr<F>
102where
103    F: MessageFormatter,
104{
105    /// Thread-safe access to the formatting logic.
106    formatter: Mutex<F>,
107}
108
109impl<F> Cerr<F>
110where
111    F: MessageFormatter,
112{
113    /// Creates a new [`Cerr`] on the heap.
114    pub fn new() -> Box<Self> {
115        Box::new(Self {
116            formatter: Mutex::new(Default::default()),
117        })
118    }
119
120    /// Creates a new [`Cerr`] service on the heap with a custom [formatter][`MessageFormatter`].
121    pub fn with_formatter(formatter: F) -> Box<Self> {
122        Box::new(Self {
123            formatter: Mutex::new(formatter),
124        })
125    }
126}
127
128impl<F> Service for Cerr<F>
129where
130    F: MessageFormatter + 'static,
131{
132    fn status(&self) -> LoggerStatus {
133        LoggerStatus::Running
134    }
135
136    /// Acquires the formatter lock and writes to the global `stderr`.
137    ///
138    /// # Errors
139    /// Returns [`ServiceError::LockPoisoned`] if the internal formatter mutex is poisoned.
140    fn work(&self, msg: &Message) -> Result<(), ServiceError> {
141        let mut formatter_guard = self.formatter.lock()?;
142        let mut out = std::io::stderr();
143        formatter_guard.format_io(msg, &mut out)?;
144        Ok(())
145    }
146
147    fn as_any(&self) -> &dyn Any {
148        self
149    }
150}
151
152impl<F> Fallback for Cerr<F>
153where
154    F: MessageFormatter + 'static,
155{
156    /// Fallback for `stderr` failures. Paradoxically attempts to log the
157    /// failure to `stdout` as a last-resort communication channel.
158    fn fallback(&self, error: &ServiceError, msg: &Message) {
159        if let Ok(mut guard) = self.formatter.lock() {
160            let mut out = std::io::stdout();
161            let _ = guard.format_io(msg, &mut out);
162            let _ = println!("CerrWriteService Error: {}", error);
163        }
164    }
165}
166
167/// A [`Cerr`] service pre-configured with the [`StandardMessageFormatter`].
168pub type StandardCerr = Cerr<StandardMessageFormatter>;