timber_rust/service/write/fmt.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/// An internal wrapper for data protected by the [`FmtWrite`] service mutex.
12///
13/// This structure ensures that both the [`MessageFormatter`] and the [`std::fmt::Write`]
14/// destination are kept together, allowing them to be borrowed mutably as a single unit
15/// once the lock is acquired.
16struct FmtData<W, F>
17where
18 W: std::fmt::Write + Send + Sync,
19 F: MessageFormatter,
20{
21 /// The destination for formatted string data.
22 /// Common types include [`String`] or a custom buffer.
23 writer: W,
24 /// The formatting strategy used to transform a [`Message`] into a string.
25 formatter: F,
26}
27
28/// A thread-safe logging [`Service`] for string-based output destinations.
29///
30/// [`Fmt`] service implements the [`Service`] trait for types that satisfy [`std::fmt::Write`].
31/// It is ideal for in-memory logging, testing, or targets that do not use byte-oriented I/O.
32///
33/// ### Thread Safety
34/// The internal `FmtWriteData` is wrapped in a [`Mutex`]. This allows the [`Service`]
35/// to be shared across threads ([`Send`] + [`Sync`]), while ensuring that only one thread
36/// can perform a [`work`](Self::work) operation at a time.
37pub struct Fmt<W, F>
38where
39 W: std::fmt::Write + Send + Sync,
40 F: MessageFormatter,
41{
42 /// A mutex-protected container holding the writer and formatter.
43 /// Access is managed via [`unlock_guard`].
44 writer: Mutex<FmtData<W, F>>,
45}
46
47impl<W, F> Fmt<W, F>
48where
49 W: std::fmt::Write + Send + Sync,
50 F: MessageFormatter + Default,
51{
52 /// Creates a new, heap-allocated [`Fmt`] service.
53 ///
54 /// # Parameters
55 /// - `writer`: An object implementing [`std::fmt::Write`].
56 /// - `formatter`: An object implementing [`MessageFormatter`].
57 ///
58 /// # Example
59 /// ```
60 /// # use timber_rust::{QueuedLogger, Logger};
61 /// # use timber_rust::service::FmtWrite;
62 /// # use timber_rust::service::write::StandardMessageFormatter;
63 /// let service = FmtWrite::<String, StandardMessageFormatter>::new(String::new());
64 /// let logger = QueuedLogger::new(service, 3, 4); // 3 retries, 4 worker threads
65 /// let logger = Logger::new(logger);
66 /// ```
67 pub fn new(writer: W) -> Box<Self> {
68 Box::new(Self {
69 writer: Mutex::new(FmtData {
70 writer,
71 formatter: F::default(),
72 }),
73 })
74 }
75
76 /// Creates a new, heap-allocated [`Fmt`] service with a custom [formatter][`MessageFormatter`].
77 ///
78 /// # Parameters
79 /// - `writer`: An object implementing [`std::fmt::Write`].
80 /// - `formatter`: An object implementing [`MessageFormatter`].
81 ///
82 /// # Example
83 /// ```
84 /// # use timber_rust::{QueuedLogger, Logger};
85 /// # use timber_rust::service::write::StandardMessageFormatter;
86 /// # use timber_rust::service::FmtWrite;
87 /// let service = FmtWrite::<String, StandardMessageFormatter>::new(String::new());
88 /// let logger = QueuedLogger::new(service, 3, 4); // 3 retries, 4 worker threads
89 /// let logger = Logger::new(logger);
90 /// ```
91 pub fn with_formatter(writer: W, formatter: F) -> Box<Self> {
92 Box::new(Self {
93 writer: Mutex::new(FmtData { writer, formatter }),
94 })
95 }
96
97 /// Allows safe, read-only access to the internal buffer without stopping the logger.
98 ///
99 /// Use this to "peek" at logs while the application is still running—perfect for
100 /// health-check endpoints that expose recent logs or for verifying state in tests.
101 ///
102 /// ### Thread Safety
103 /// This method acquires a mutex lock. While the closure `f` is executing, any
104 /// incoming logs from other threads will **block** until the closure returns.
105 /// Keep the logic inside the closure as fast as possible.
106 ///
107 /// ### Returns
108 /// - [`Some(R)`][`Some`]: The result of your closure if the lock was acquired.
109 /// - [`None`]: If the internal lock was poisoned by a previous panic.
110 pub fn inspect_writer<R>(&self, f: impl FnOnce(&W) -> R) -> Option<R> {
111 self.writer.lock().ok().map(|data| f(&data.writer))
112 }
113
114 /// Destroys the [`Service`] and reclaims ownership of the underlying buffer or writer.
115 ///
116 /// Use this at the end of a program, a test case, or a lifecycle stage to extract
117 /// all recorded logs and free up the resources used by the [`Service`].
118 ///
119 /// ### Ownership & Lifecycle
120 /// This method consumes `self`, meaning the [`Fmt`] service can no longer be
121 /// used after this call. This is the only way to get full, non-cloned ownership
122 /// of the internal writer (e.g., a [`String`] or [`Vec<u8>`]).
123 ///
124 /// ### Guarantees
125 /// Because this takes ownership of the [`Service`], it is compile-time guaranteed
126 /// that no other threads can be writing to the buffer when this is called.
127 pub fn recover_writer(self) -> Result<W, ServiceError> {
128 let data = self.writer.into_inner();
129 match data {
130 Ok(data) => Ok(data.writer),
131 Err(_) => Err(ServiceError::LockPoisoned),
132 }
133 }
134
135 /// Clears the underlying writer if the type supports it (e.g., String).
136 /// Useful for reusing the [`Service`] in benchmarks or test suites.
137 pub fn clear_writer(&self)
138 where
139 W: Default,
140 {
141 if let Ok(mut data) = self.writer.lock() {
142 data.writer = W::default();
143 }
144 }
145}
146
147impl<W, F> Service for Fmt<W, F>
148where
149 W: std::fmt::Write + Send + Sync + 'static,
150 F: MessageFormatter + 'static,
151{
152 /// Returns the current operational status.
153 /// Currently always returns [`LoggerStatus::Running`].
154 fn status(&self) -> LoggerStatus {
155 LoggerStatus::Running
156 }
157
158 /// Acquires a lock and writes a formatted [`Message`] to the internal writer.
159 ///
160 /// This method uses [`MessageFormatter::format_fmt`] to perform the write.
161 ///
162 /// # Errors
163 /// - Returns [`ServiceError::LockPoisoned`] if the internal mutex is poisoned.
164 /// - Forwards any [`ServiceError`] returned by the formatter.
165 fn work(&self, msg: &Message) -> Result<(), ServiceError> {
166 let mut guard = self.writer.lock()?;
167 // Destructure the guard to get mutable access to fields
168 let FmtData {
169 formatter, writer, ..
170 } = &mut *guard;
171 formatter.format_fmt(msg, writer)?;
172 Ok(())
173 }
174
175 /// Returns a reference to the underlying type as [Any] for downcasting.
176 fn as_any(&self) -> &dyn Any {
177 self
178 }
179}
180impl<W, F> Fallback for Fmt<W, F>
181where
182 W: std::fmt::Write + Send + Sync + 'static,
183 F: MessageFormatter + 'static,
184{
185 /// Attempts to log an error to `stdout` if the primary [`work`](Self::work) call fails.
186 ///
187 /// This method performs a best-effort write. If the mutex is locked by a hanging
188 /// thread, the fallback will be skipped to avoid a deadlock.
189 fn fallback(&self, error: &ServiceError, msg: &Message) {
190 if let Ok(mut guard) = self.writer.lock() {
191 let mut out = std::io::stdout();
192 let _ = guard.formatter.format_io(msg, &mut out);
193 let _ = eprintln!("FmtWriteService Error: {}", error);
194 }
195 }
196}
197
198/// A specialized alias for logging directly into a [`String`].
199///
200/// This is commonly used for unit testing or collecting logs for display in a UI.
201///
202/// **Note:** Trait bounds on `F` are not enforced at definition time but are
203/// checked during instantiation.
204#[allow(type_alias_bounds)]
205pub type StringFmt<F: MessageFormatter> = Fmt<String, F>;
206
207/// A [`StringFmt`] service pre-configured with the [`StandardMessageFormatter`].
208///
209/// This provides a zero-configuration path for in-memory string logging.
210pub type StandardStringFmt = Fmt<String, StandardMessageFormatter>;