timber_rust/factory/
fmt.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Dante Doménech Martinez dante19031999@gmail.com
3
4use crate::service::{StandardWriteMessageFormatter, WriteMessageFormatter};
5use crate::{service, Concurrency, DirectLogger, Logger, QueuedLogger};
6
7/// A specialized factory for creating loggers that write to memory-oriented destinations.
8///
9/// The `FmtFactory` acts as the entry point for any output that implements [`std::fmt::Write`].
10/// It allows you to configure global retry and threading policies before selecting a
11/// specific output target (like a String writer).
12///
13/// ### Default Configuration
14/// - **Max Retries**: 3 (Attempts to re-send if a write failure occurs).
15/// - **Worker Count**: 1 (Single background thread to maintain message order).
16pub struct FmtWrite {
17    max_retries: usize,
18    worker_count: usize,
19}
20
21/// A concrete builder state for a specific writer type `W`.
22///
23/// Once a writer is provided (via [`FmtWrite::string`], etc.),
24/// this struct allows you to finalize the logger by choosing a [`Concurrency`] model.
25pub struct TypedFmtWrite<W>
26where
27    W: std::fmt::Write + Send + Sync + 'static,
28{
29    writer: W,
30    max_retries: usize,
31    worker_count: usize,
32}
33
34/// A concrete builder state for a specific boxed writer type `W`.
35///
36/// Once a writer is provided (via [`FmtWrite::boxed`], etc.),
37/// this struct allows you to finalize the logger by choosing a [`Concurrency`] model.
38pub struct BoxedFmtWrite {
39    writer: Box<dyn std::fmt::Write + Send + Sync>,
40    max_retries: usize,
41    worker_count: usize,
42}
43
44/// A pre-configured factory for logging directly to a fmt [`String`].
45pub type StringFmt = TypedFmtWrite<String>;
46
47impl FmtWrite {
48    /// Specializes the factory to log directly to a [`String`].
49    ///
50    /// This is the most direct path for string logging. Each log entry is
51    /// appended directly into an string.
52    pub fn string(self) -> StringFmt {
53        StringFmt {
54            writer: String::with_capacity(1024),
55            max_retries: self.max_retries,
56            worker_count: self.worker_count,
57        }
58    }
59
60    /// Specializes the factory to log directly to a [`String`].
61    /// Allows to set a custom capacity.
62    ///
63    /// This is the most direct path for string logging. Each log entry is
64    /// appended directly into an string.
65    pub fn string_with_capacity(self, capacity: usize) -> StringFmt {
66        StringFmt {
67            writer: String::with_capacity(capacity),
68            max_retries: self.max_retries,
69            worker_count: self.worker_count,
70        }
71    }
72
73    /// Specializes the factory to log directly to a [`String`].
74    /// Allows to set an already existing string as the destiny.
75    ///
76    /// This is the most direct path for string logging. Each log entry is
77    /// appended directly into an string.
78    pub fn string_sullied(self, writer: String) -> StringFmt {
79        StringFmt {
80            writer,
81            max_retries: self.max_retries,
82            worker_count: self.worker_count,
83        }
84    }
85
86    /// Specializes the factory for any generic type implementing [`std::fmt::Write`].
87    pub fn writer<W>(self, writer: W) -> TypedFmtWrite<W>
88    where
89        W: std::fmt::Write + Send + Sync + 'static,
90    {
91        TypedFmtWrite {
92            writer,
93            max_retries: self.max_retries,
94            worker_count: self.worker_count,
95        }
96    }
97
98    /// Specializes the factory for any generic type implementing `Box<dyn std::fmt::Write + Send + Sync>`.
99    pub fn boxed(self, writer: Box<dyn std::fmt::Write + Send + Sync>) -> BoxedFmtWrite {
100        BoxedFmtWrite {
101            writer,
102            max_retries: self.max_retries,
103            worker_count: self.worker_count,
104        }
105    }
106
107    /// Configures the maximum number of retries for the resulting service.
108    pub fn max_retries(self, max_retries: usize) -> Self {
109        Self {
110            max_retries,
111            ..self
112        }
113    }
114
115    /// Configures the background worker thread count for asynchronous logging.
116    pub fn worker_count(self, worker_count: usize) -> Self {
117        Self {
118            worker_count,
119            ..self
120        }
121    }
122
123    /// Finalizes the logger using a specific writer and a [`Concurrency`] strategy.
124    /// This uses the default [`StandardWriteMessageFormatter`].
125    pub fn build<W>(self, concurrency: Concurrency, writer: W) -> Logger
126    where
127        W: std::fmt::Write + Send + Sync + 'static,
128    {
129        match concurrency {
130            Concurrency::Sync => Logger::new(self.build_impl_direct(writer)),
131            Concurrency::Async => Logger::new(self.build_impl_queued(writer)),
132        }
133    }
134
135    /// Builds a [`DirectLogger`] implementation wrapped in a [`Box`].
136    pub fn build_impl_direct<W>(self, writer: W) -> Box<DirectLogger>
137    where
138        W: std::fmt::Write + Send + Sync + 'static,
139    {
140        let max_retries = self.max_retries;
141        DirectLogger::new(self.build_service(writer), max_retries)
142    }
143
144    /// Builds a [`QueuedLogger`] implementation wrapped in a [`Box`].
145    pub fn build_impl_queued<W>(self, writer: W) -> Box<QueuedLogger>
146    where
147        W: std::fmt::Write + Send + Sync + 'static,
148    {
149        let max_retries = self.max_retries;
150        let worker_count = self.worker_count;
151        QueuedLogger::new(self.build_service(writer), max_retries, worker_count)
152    }
153
154    /// Internal helper to construct the [`service::FmtWrite`] service with the standard formatter.
155    pub fn build_service<W>(
156        self,
157        writer: W,
158    ) -> Box<service::FmtWrite<W, StandardWriteMessageFormatter>>
159    where
160        W: std::fmt::Write + Send + Sync + 'static,
161    {
162        service::FmtWrite::new(writer)
163    }
164
165    /// Finalizes the logger using a custom formatter and a [`Concurrency`] strategy.
166    pub fn build_with_formatter<W, MF>(
167        self,
168        concurrency: Concurrency,
169        writer: W,
170        formatter: MF,
171    ) -> Logger
172    where
173        MF: WriteMessageFormatter + 'static,
174        W: std::fmt::Write + Send + Sync + 'static,
175    {
176        match concurrency {
177            Concurrency::Sync => {
178                Logger::new(self.build_impl_direct_with_formatter(writer, formatter))
179            }
180            Concurrency::Async => {
181                Logger::new(self.build_impl_queued_with_formatter(writer, formatter))
182            }
183        }
184    }
185
186    /// Builds a [`DirectLogger`] with a custom formatter.
187    pub fn build_impl_direct_with_formatter<W, MF>(
188        self,
189        writer: W,
190        formatter: MF,
191    ) -> Box<DirectLogger>
192    where
193        MF: WriteMessageFormatter + 'static,
194        W: std::fmt::Write + Send + Sync + 'static,
195    {
196        let max_retries = self.max_retries;
197        DirectLogger::new(
198            self.build_service_with_formatter(writer, formatter),
199            max_retries,
200        )
201    }
202
203    /// Builds a [`QueuedLogger`] with a custom formatter.
204    pub fn build_impl_queued_with_formatter<W, MF>(
205        self,
206        writer: W,
207        formatter: MF,
208    ) -> Box<QueuedLogger>
209    where
210        MF: WriteMessageFormatter + 'static,
211        W: std::fmt::Write + Send + Sync + 'static,
212    {
213        let max_retries = self.max_retries;
214        let worker_count = self.worker_count;
215        QueuedLogger::new(
216            self.build_service_with_formatter(writer, formatter),
217            max_retries,
218            worker_count,
219        )
220    }
221
222    /// Internal helper to construct the [`service::FmtWrite`] service with a custom formatter.
223    pub fn build_service_with_formatter<W, MF>(
224        self,
225        writer: W,
226        formatter: MF,
227    ) -> Box<service::FmtWrite<W, MF>>
228    where
229        MF: WriteMessageFormatter + 'static,
230        W: std::fmt::Write + Send + Sync + 'static,
231    {
232        service::FmtWrite::with_formatter(writer, formatter)
233    }
234}
235
236impl Default for FmtWrite {
237    /// Provides sensible defaults for byte-oriented logging.
238    ///
239    /// - **max_retries**: `3` (Standard resilience against transient I/O issues).
240    /// - **worker_count**: `1` (Ensures sequential log ordering in asynchronous mode).
241    fn default() -> Self {
242        Self {
243            max_retries: 3,
244            worker_count: 1,
245        }
246    }
247}
248
249impl<W> TypedFmtWrite<W>
250where
251    W: std::fmt::Write + Send + Sync + 'static,
252{
253    /// Creates a new [`TypedFmtWrite`] with a specific writer and default policies.
254    ///
255    /// Defaults to 3 retries and 1 worker thread.
256    pub fn new(writer: W) -> Self {
257        Self {
258            writer,
259            max_retries: 3,
260            worker_count: 1,
261        }
262    }
263
264    /// Returns a reference to the underlying writer.
265    pub fn get_writer(&self) -> &W {
266        &self.writer
267    }
268
269    /// Returns the currently configured maximum retry attempts.
270    pub fn get_max_retries(&self) -> usize {
271        self.max_retries
272    }
273
274    /// Returns the currently configured background worker count.
275    pub fn get_worker_count(&self) -> usize {
276        self.worker_count
277    }
278
279    /// Replaces the current writer while keeping existing retry and worker configurations.
280    pub fn writer(self, writer: W) -> Self {
281        Self { writer, ..self }
282    }
283
284    /// Updates the maximum number of retry attempts for this specific writer.
285    pub fn max_retries(self, max_retries: usize) -> Self {
286        Self {
287            max_retries,
288            ..self
289        }
290    }
291
292    /// Updates the background worker count for this specific writer.
293    pub fn worker_count(self, worker_count: usize) -> Self {
294        Self {
295            worker_count,
296            ..self
297        }
298    }
299
300    /// Finalizes the builder and returns a high-level [`Logger`].
301    ///
302    /// This uses the default [`StandardWriteMessageFormatter`].
303    pub fn build(self, concurrency: Concurrency) -> Logger {
304        match concurrency {
305            Concurrency::Sync => Logger::new(self.build_impl_direct()),
306            Concurrency::Async => Logger::new(self.build_impl_queued()),
307        }
308    }
309
310    /// Builds the underlying [`DirectLogger`] implementation for this writer.
311    ///
312    /// Useful if you need to bypass the [`Logger`] wrapper and manage the
313    /// synchronous driver manually.
314    pub fn build_impl_direct(self) -> Box<DirectLogger> {
315        let max_retries = self.max_retries;
316        DirectLogger::new(self.build_service(), max_retries)
317    }
318
319    /// Builds the underlying [`QueuedLogger`] implementation for this writer.
320    ///
321    /// Useful if you need to bypass the [`Logger`] wrapper and manage the
322    /// asynchronous worker pool manually.
323    pub fn build_impl_queued(self) -> Box<QueuedLogger> {
324        let max_retries = self.max_retries;
325        let worker_count = self.worker_count;
326        QueuedLogger::new(self.build_service(), max_retries, worker_count)
327    }
328
329    /// Internal helper to construct the [`service::FmtWrite`] service for this specific writer
330    /// using the standard formatter.
331    pub fn build_service(self) -> Box<service::FmtWrite<W, StandardWriteMessageFormatter>> {
332        service::FmtWrite::new(self.writer)
333    }
334
335    /// Finalizes the builder using a custom [`WriteMessageFormatter`].
336    ///
337    /// This allows you to define exactly how messages are serialized (e.g., JSON,
338    /// custom text headers) before being sent to the writer.
339    pub fn build_with_formatter<MF>(self, concurrency: Concurrency, formatter: MF) -> Logger
340    where
341        MF: WriteMessageFormatter + 'static,
342    {
343        match concurrency {
344            Concurrency::Sync => Logger::new(self.build_impl_direct_with_formatter(formatter)),
345            Concurrency::Async => Logger::new(self.build_impl_queued_with_formatter(formatter)),
346        }
347    }
348
349    /// Builds a [`DirectLogger`] with a specific formatter for this writer.
350    pub fn build_impl_direct_with_formatter<MF>(self, formatter: MF) -> Box<DirectLogger>
351    where
352        MF: WriteMessageFormatter + 'static,
353    {
354        let max_retries = self.max_retries;
355        DirectLogger::new(self.build_service_with_formatter(formatter), max_retries)
356    }
357
358    /// Builds a [`QueuedLogger`] with a specific formatter for this writer.
359    pub fn build_impl_queued_with_formatter<MF>(self, formatter: MF) -> Box<QueuedLogger>
360    where
361        MF: WriteMessageFormatter + 'static,
362    {
363        let max_retries = self.max_retries;
364        let worker_count = self.worker_count;
365        QueuedLogger::new(
366            self.build_service_with_formatter(formatter),
367            max_retries,
368            worker_count,
369        )
370    }
371
372    /// Internal helper to construct the [`service::FmtWrite`] service for this specific writer
373    /// using a custom formatter.
374    pub fn build_service_with_formatter<MF>(self, formatter: MF) -> Box<service::FmtWrite<W, MF>>
375    where
376        MF: WriteMessageFormatter + 'static,
377    {
378        service::FmtWrite::with_formatter(self.writer, formatter)
379    }
380}
381
382impl BoxedFmtWrite {
383    /// Creates a new [`TypedFmtWrite`] with a specific writer and default policies.
384    ///
385    /// Defaults to 3 retries and 1 worker thread.
386    pub fn new(writer: Box<dyn std::fmt::Write + Send + Sync>) -> Self {
387        Self {
388            writer,
389            max_retries: 3,
390            worker_count: 1,
391        }
392    }
393
394    /// Returns a reference to the underlying writer.
395    pub fn get_writer(&self) -> &(dyn std::fmt::Write + Send + Sync) {
396        self.writer.as_ref()
397    }
398
399    /// Returns the currently configured maximum retry attempts.
400    pub fn get_max_retries(&self) -> usize {
401        self.max_retries
402    }
403
404    /// Returns the currently configured background worker count.
405    pub fn get_worker_count(&self) -> usize {
406        self.worker_count
407    }
408
409    /// Replaces the current writer while keeping existing retry and worker configurations.
410    pub fn writer(self, writer: Box<dyn std::fmt::Write + Send + Sync>) -> Self {
411        Self { writer, ..self }
412    }
413
414    /// Updates the maximum number of retry attempts for this specific writer.
415    pub fn max_retries(self, max_retries: usize) -> Self {
416        Self {
417            max_retries,
418            ..self
419        }
420    }
421
422    /// Updates the background worker count for this specific writer.
423    pub fn worker_count(self, worker_count: usize) -> Self {
424        Self {
425            worker_count,
426            ..self
427        }
428    }
429
430    /// Finalizes the builder and returns a high-level [`Logger`].
431    ///
432    /// This uses the default [`StandardWriteMessageFormatter`].
433    pub fn build(self, concurrency: Concurrency) -> Logger {
434        match concurrency {
435            Concurrency::Sync => Logger::new(self.build_impl_direct()),
436            Concurrency::Async => Logger::new(self.build_impl_queued()),
437        }
438    }
439
440    /// Builds the underlying [`DirectLogger`] implementation for this writer.
441    ///
442    /// Useful if you need to bypass the [`Logger`] wrapper and manage the
443    /// synchronous driver manually.
444    pub fn build_impl_direct(self) -> Box<DirectLogger> {
445        let max_retries = self.max_retries;
446        DirectLogger::new(self.build_service(), max_retries)
447    }
448
449    /// Builds the underlying [`QueuedLogger`] implementation for this writer.
450    ///
451    /// Useful if you need to bypass the [`Logger`] wrapper and manage the
452    /// asynchronous worker pool manually.
453    pub fn build_impl_queued(self) -> Box<QueuedLogger> {
454        let max_retries = self.max_retries;
455        let worker_count = self.worker_count;
456        QueuedLogger::new(self.build_service(), max_retries, worker_count)
457    }
458
459    /// Internal helper to construct the [`service::BoxedFmtWrite`] service for this specific writer
460    /// using the standard formatter.
461    pub fn build_service(self) -> Box<service::BoxedFmtWrite<StandardWriteMessageFormatter>> {
462        service::BoxedFmtWrite::new(self.writer)
463    }
464
465    /// Finalizes the builder using a custom [`WriteMessageFormatter`].
466    ///
467    /// This allows you to define exactly how messages are serialized (e.g., JSON,
468    /// custom text headers) before being sent to the writer.
469    pub fn build_with_formatter<MF>(self, concurrency: Concurrency, formatter: MF) -> Logger
470    where
471        MF: WriteMessageFormatter + 'static,
472    {
473        match concurrency {
474            Concurrency::Sync => Logger::new(self.build_impl_direct_with_formatter(formatter)),
475            Concurrency::Async => Logger::new(self.build_impl_queued_with_formatter(formatter)),
476        }
477    }
478
479    /// Builds a [`DirectLogger`] with a specific formatter for this writer.
480    pub fn build_impl_direct_with_formatter<MF>(self, formatter: MF) -> Box<DirectLogger>
481    where
482        MF: WriteMessageFormatter + 'static,
483    {
484        let max_retries = self.max_retries;
485        DirectLogger::new(self.build_service_with_formatter(formatter), max_retries)
486    }
487
488    /// Builds a [`QueuedLogger`] with a specific formatter for this writer.
489    pub fn build_impl_queued_with_formatter<MF>(self, formatter: MF) -> Box<QueuedLogger>
490    where
491        MF: WriteMessageFormatter + 'static,
492    {
493        let max_retries = self.max_retries;
494        let worker_count = self.worker_count;
495        QueuedLogger::new(
496            self.build_service_with_formatter(formatter),
497            max_retries,
498            worker_count,
499        )
500    }
501
502    /// Internal helper to construct the [`service::BoxedFmtWrite`] service for this specific writer
503    /// using a custom formatter.
504    pub fn build_service_with_formatter<MF>(self, formatter: MF) -> Box<service::BoxedFmtWrite<MF>>
505    where
506        MF: WriteMessageFormatter + 'static,
507    {
508        service::BoxedFmtWrite::with_formatter(self.writer, formatter)
509    }
510}