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