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>;