timber_rust/factory/
io.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::{Concurrency, DirectLogger, Logger, QueuedLogger, service};
6use std::fs::File;
7use std::io::BufWriter;
8
9/// A specialized factory for creating loggers that write to byte-oriented destinations.
10///
11/// The `IoFactory` acts as the entry point for any output that implements [`std::io::Write`].
12/// It allows you to configure global retry and threading policies before selecting a
13/// specific output target (like a File or a Buffered Writer).
14///
15/// ### Default Configuration
16/// - **Max Retries**: 3 (Attempts to re-send if a write failure occurs).
17/// - **Worker Count**: 1 (Single background thread to maintain message order).
18pub struct IoWrite {
19    max_retries: usize,
20    worker_count: usize,
21}
22
23/// A concrete builder state for a specific writer type `W`.
24///
25/// Once a writer is provided (via [`IoWrite::file`], [`IoWrite::buffered_file`], etc.),
26/// this struct allows you to finalize the logger by choosing a [`Concurrency`] model.
27pub struct TypedIoWrite<W>
28where
29    W: std::io::Write + Send + Sync + 'static,
30{
31    writer: W,
32    max_retries: usize,
33    worker_count: usize,
34}
35
36/// A pre-configured factory for logging directly to a filesystem [`File`].
37/// Use this when you need "Fire and Forget" durability where logs hit the OS immediately.
38pub type FileIoFactory = TypedIoWrite<File>;
39
40/// A pre-configured factory for logging to a [`BufWriter<File>`].
41///
42/// **Performance Tip**: This is significantly faster than a raw `File` for high-frequency
43/// logging because it reduces the number of expensive System Calls by batching writes
44/// in memory.
45pub type BufferedFileIoFactory = TypedIoWrite<BufWriter<File>>;
46
47/// A pre-configured factory for logging to a boxed [`Write`][std::io::Write] trait object.
48/// Useful for plugin systems or when the underlying writer type is erased.
49pub type BoxedIoFactory = TypedIoWrite<Box<dyn std::io::Write + Send + Sync>>;
50
51impl IoWrite {
52    /// Specializes the factory to log directly to a filesystem [`File`].
53    ///
54    /// This is the most direct path for file logging. Each log entry is
55    /// dispatched immediately to the OS file handle.
56    pub fn file(self, file: File) -> FileIoFactory {
57        FileIoFactory {
58            writer: file,
59            max_retries: self.max_retries,
60            worker_count: self.worker_count,
61        }
62    }
63
64    /// Specializes the factory to log to a [`BufWriter<File>`].
65    ///
66    /// **Performance Choice**: By batching small writes into a memory buffer,
67    /// this reduces the frequency of expensive system calls. Highly recommended
68    /// for high-throughput applications.
69    pub fn buffered_file(self, file: BufWriter<File>) -> BufferedFileIoFactory {
70        BufferedFileIoFactory {
71            writer: file,
72            max_retries: self.max_retries,
73            worker_count: self.worker_count,
74        }
75    }
76
77    /// Specializes the factory for a boxed [`Write`][std::io::Write] trait object.
78    ///
79    /// Useful when the concrete writer type is erased or determined at runtime.
80    pub fn boxed(self, writer: Box<dyn std::io::Write + Send + Sync>) -> BoxedIoFactory {
81        BoxedIoFactory {
82            writer,
83            max_retries: self.max_retries,
84            worker_count: self.worker_count,
85        }
86    }
87
88    /// Specializes the factory for any generic type implementing [`std::io::Write`].
89    pub fn writer<W>(self, writer: W) -> TypedIoWrite<W>
90    where
91        W: std::io::Write + Send + Sync + 'static,
92    {
93        TypedIoWrite {
94            writer,
95            max_retries: self.max_retries,
96            worker_count: self.worker_count,
97        }
98    }
99
100    /// Configures the maximum number of retries for the resulting service.
101    pub fn max_retries(self, max_retries: usize) -> Self {
102        Self {
103            max_retries,
104            ..self
105        }
106    }
107
108    /// Configures the background worker thread count for asynchronous logging.
109    pub fn worker_count(self, worker_count: usize) -> Self {
110        Self {
111            worker_count,
112            ..self
113        }
114    }
115
116    /// Finalizes the logger using a specific writer and a [`Concurrency`] strategy.
117    /// This uses the default [`StandardWriteMessageFormatter`].
118    pub fn build<W>(self, concurrency: Concurrency, writer: W) -> Logger
119    where
120        W: std::io::Write + Send + Sync + 'static,
121    {
122        match concurrency {
123            Concurrency::Sync => Logger::new(self.build_impl_direct(writer)),
124            Concurrency::Async => Logger::new(self.build_impl_queued(writer)),
125        }
126    }
127
128    /// Builds a [`DirectLogger`] implementation wrapped in a [`Box`].
129    pub fn build_impl_direct<W>(self, writer: W) -> Box<DirectLogger>
130    where
131        W: std::io::Write + Send + Sync + 'static,
132    {
133        let max_retries = self.max_retries;
134        DirectLogger::new(self.build_service(writer), max_retries)
135    }
136
137    /// Builds a [`QueuedLogger`] implementation wrapped in a [`Box`].
138    pub fn build_impl_queued<W>(self, writer: W) -> Box<QueuedLogger>
139    where
140        W: std::io::Write + Send + Sync + 'static,
141    {
142        let max_retries = self.max_retries;
143        let worker_count = self.worker_count;
144        QueuedLogger::new(self.build_service(writer), max_retries, worker_count)
145    }
146
147    /// Internal helper to construct the [`service::IoWrite`] service with the standard formatter.
148    pub fn build_service<W>(self, writer: W) -> Box<service::IoWrite<W, StandardWriteMessageFormatter>>
149    where
150        W: std::io::Write + Send + Sync + 'static,
151    {
152        service::IoWrite::new(writer)
153    }
154
155    /// Finalizes the logger using a custom formatter and a [`Concurrency`] strategy.
156    pub fn build_with_formatter<W, MF>(
157        self,
158        concurrency: Concurrency,
159        writer: W,
160        formatter: MF,
161    ) -> Logger
162    where
163        MF: WriteMessageFormatter + 'static,
164        W: std::io::Write + Send + Sync + 'static,
165    {
166        match concurrency {
167            Concurrency::Sync => {
168                Logger::new(self.build_impl_direct_with_formatter(writer, formatter))
169            }
170            Concurrency::Async => {
171                Logger::new(self.build_impl_queued_with_formatter(writer, formatter))
172            }
173        }
174    }
175
176    /// Builds a [`DirectLogger`] with a custom formatter.
177    pub fn build_impl_direct_with_formatter<W, MF>(
178        self,
179        writer: W,
180        formatter: MF,
181    ) -> Box<DirectLogger>
182    where
183        MF: WriteMessageFormatter + 'static,
184        W: std::io::Write + Send + Sync + 'static,
185    {
186        let max_retries = self.max_retries;
187        DirectLogger::new(
188            self.build_service_with_formatter(writer, formatter),
189            max_retries,
190        )
191    }
192
193    /// Builds a [`QueuedLogger`] with a custom formatter.
194    pub fn build_impl_queued_with_formatter<W, MF>(
195        self,
196        writer: W,
197        formatter: MF,
198    ) -> Box<QueuedLogger>
199    where
200        MF: WriteMessageFormatter + 'static,
201        W: std::io::Write + Send + Sync + 'static,
202    {
203        let max_retries = self.max_retries;
204        let worker_count = self.worker_count;
205        QueuedLogger::new(
206            self.build_service_with_formatter(writer, formatter),
207            max_retries,
208            worker_count,
209        )
210    }
211
212    /// Internal helper to construct the [`service::IoWrite`] service with a custom formatter.
213    pub fn build_service_with_formatter<W, MF>(self, writer: W, formatter: MF) -> Box<service::IoWrite<W, MF>>
214    where
215        MF: WriteMessageFormatter + 'static,
216        W: std::io::Write + Send + Sync + 'static,
217    {
218        service::IoWrite::with_formatter(writer, formatter)
219    }
220}
221
222impl Default for IoWrite {
223    /// Provides sensible defaults for byte-oriented logging.
224    ///
225    /// - **max_retries**: `3` (Standard resilience against transient I/O issues).
226    /// - **worker_count**: `1` (Ensures sequential log ordering in asynchronous mode).
227    fn default() -> Self {
228        Self {
229            max_retries: 3,
230            worker_count: 1,
231        }
232    }
233}
234
235impl<W> TypedIoWrite<W>
236where
237    W: std::io::Write + Send + Sync + 'static,
238{
239    /// Creates a new [`TypedIoWrite`] with a specific writer and default policies.
240    ///
241    /// Defaults to 3 retries and 1 worker thread.
242    pub fn new(writer: W) -> Self {
243        Self {
244            writer,
245            max_retries: 3,
246            worker_count: 1,
247        }
248    }
249
250    /// Returns a reference to the underlying writer.
251    pub fn get_writer(&self) -> &W {
252        &self.writer
253    }
254
255    /// Returns the currently configured maximum retry attempts.
256    pub fn get_max_retries(&self) -> usize {
257        self.max_retries
258    }
259
260    /// Returns the currently configured background worker count.
261    pub fn get_worker_count(&self) -> usize {
262        self.worker_count
263    }
264
265    /// Replaces the current writer while keeping existing retry and worker configurations.
266    pub fn writer(self, writer: W) -> Self {
267        Self { writer, ..self }
268    }
269
270    /// Updates the maximum number of retry attempts for this specific writer.
271    pub fn max_retries(self, max_retries: usize) -> Self {
272        Self {
273            max_retries,
274            ..self
275        }
276    }
277
278    /// Updates the background worker count for this specific writer.
279    pub fn worker_count(self, worker_count: usize) -> Self {
280        Self {
281            worker_count,
282            ..self
283        }
284    }
285
286    /// Finalizes the builder and returns a high-level [`Logger`].
287    ///
288    /// This uses the default [`StandardWriteMessageFormatter`].
289    pub fn build(self, concurrency: Concurrency) -> Logger {
290        match concurrency {
291            Concurrency::Sync => Logger::new(self.build_impl_direct()),
292            Concurrency::Async => Logger::new(self.build_impl_queued()),
293        }
294    }
295
296    /// Builds the underlying [`DirectLogger`] implementation for this writer.
297    ///
298    /// Useful if you need to bypass the [`Logger`] wrapper and manage the
299    /// synchronous driver manually.
300    pub fn build_impl_direct(self) -> Box<DirectLogger> {
301        let max_retries = self.max_retries;
302        DirectLogger::new(self.build_service(), max_retries)
303    }
304
305    /// Builds the underlying [`QueuedLogger`] implementation for this writer.
306    ///
307    /// Useful if you need to bypass the [`Logger`] wrapper and manage the
308    /// asynchronous worker pool manually.
309    pub fn build_impl_queued(self) -> Box<QueuedLogger> {
310        let max_retries = self.max_retries;
311        let worker_count = self.worker_count;
312        QueuedLogger::new(self.build_service(), max_retries, worker_count)
313    }
314
315    /// Internal helper to construct the [`service::IoWrite`] service for this specific writer
316    /// using the standard formatter.
317    pub fn build_service(self) -> Box<service::IoWrite<W, StandardWriteMessageFormatter>> {
318        service::IoWrite::new(self.writer)
319    }
320
321    /// Finalizes the builder using a custom [`WriteMessageFormatter`].
322    ///
323    /// This allows you to define exactly how messages are serialized (e.g., JSON,
324    /// custom text headers) before being sent to the writer.
325    pub fn build_with_formatter<MF>(self, concurrency: Concurrency, formatter: MF) -> Logger
326    where
327        MF: WriteMessageFormatter + 'static,
328    {
329        match concurrency {
330            Concurrency::Sync => Logger::new(self.build_impl_direct_with_formatter(formatter)),
331            Concurrency::Async => Logger::new(self.build_impl_queued_with_formatter(formatter)),
332        }
333    }
334
335    /// Builds a [`DirectLogger`] with a specific formatter for this writer.
336    pub fn build_impl_direct_with_formatter<MF>(self, formatter: MF) -> Box<DirectLogger>
337    where
338        MF: WriteMessageFormatter + 'static,
339    {
340        let max_retries = self.max_retries;
341        DirectLogger::new(self.build_service_with_formatter(formatter), max_retries)
342    }
343
344    /// Builds a [`QueuedLogger`] with a specific formatter for this writer.
345    pub fn build_impl_queued_with_formatter<MF>(self, formatter: MF) -> Box<QueuedLogger>
346    where
347        MF: WriteMessageFormatter + 'static,
348    {
349        let max_retries = self.max_retries;
350        let worker_count = self.worker_count;
351        QueuedLogger::new(
352            self.build_service_with_formatter(formatter),
353            max_retries,
354            worker_count,
355        )
356    }
357
358    /// Internal helper to construct the [`service::IoWrite`] service for this specific writer
359    /// using a custom formatter.
360    pub fn build_service_with_formatter<MF>(self, formatter: MF) -> Box<service::IoWrite<W, MF>>
361    where
362        MF: WriteMessageFormatter + 'static,
363    {
364        service::IoWrite::with_formatter(self.writer, formatter)
365    }
366}