flexi_logger/writers/file_log_writer/
builder.rs

1use crate::flexi_error::FlexiLoggerError;
2use crate::formats::default_format;
3use crate::{Cleanup, Criterion, FileSpec, FormatFunction, Naming, WriteMode};
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use super::{FileLogWriter, FileLogWriterConfig, LogWriter, RotationConfig, State};
8
9/// Builder for [`FileLogWriter`].
10#[allow(clippy::struct_excessive_bools, clippy::module_name_repetitions)]
11pub struct FileLogWriterBuilder {
12    cfg_print_message: bool,
13    cfg_append: bool,
14    cfg_write_mode: WriteMode,
15    file_spec: FileSpec,
16    cfg_o_create_symlink: Option<PathBuf>,
17    cfg_line_ending: &'static [u8],
18    format: FormatFunction,
19    o_rotation_config: Option<RotationConfig>,
20    max_log_level: log::LevelFilter,
21    cleanup_in_background_thread: bool,
22    use_utc: bool,
23}
24
25/// Methods for influencing the behavior of the [`FileLogWriter`].
26impl FileLogWriterBuilder {
27    pub(crate) fn new(file_spec: FileSpec) -> Self {
28        Self {
29            o_rotation_config: None,
30            cfg_print_message: false,
31            file_spec,
32            cfg_append: false,
33            cfg_write_mode: WriteMode::Direct,
34            cfg_o_create_symlink: None,
35            cfg_line_ending: super::UNIX_LINE_ENDING,
36            format: default_format,
37            max_log_level: log::LevelFilter::Trace,
38            cleanup_in_background_thread: true,
39            use_utc: false,
40        }
41    }
42
43    /// Makes the [`FileLogWriter`] print an info message to stdout
44    /// when a new file is used for log-output.
45    #[must_use]
46    pub fn print_message(mut self) -> Self {
47        self.cfg_print_message = true;
48        self
49    }
50
51    /// Makes the [`FileLogWriter`] use the provided format function for the log entries,
52    /// rather than [`default_format`].
53    #[must_use]
54    pub fn format(mut self, format: FormatFunction) -> Self {
55        self.format = format;
56        self
57    }
58
59    /// Influences how the cleanup activities
60    /// (finding files, deleting files, optionally compressing files) are done
61    /// when rotation is used with some [`Cleanup`] variant.
62    ///
63    /// With the synchronous [write modes](crate::WriteMode),
64    /// the cleanup activities are done by default in a dedicated background thread, to
65    /// minimize the blocking impact on your application.
66    /// You can avoid this extra thread by calling this method with
67    /// `use_background_thread = false`; the cleanup is then done synchronously
68    /// by the thread that is currently logging and - by chance - causing a file rotation.
69    ///
70    /// With [`WriteMode::AsyncWith`](crate::WriteMode::AsyncWith),
71    /// the cleanup activities are always done by the same background thread
72    /// that also does the file I/O, this method then has no effect.
73    #[must_use]
74    pub fn cleanup_in_background_thread(mut self, use_background_thread: bool) -> Self {
75        self.cleanup_in_background_thread = use_background_thread;
76        self
77    }
78
79    /// Use rotation to prevent indefinite growth of log files.
80    ///
81    /// By default, the log file is fixed while your program is running and will grow indefinitely.
82    /// With this option being used, when the log file reaches the specified criterion,
83    /// the file will be closed and a new file will be opened.
84    ///
85    /// Note that also the filename pattern changes:
86    ///
87    /// - by default, no timestamp is added to the filename
88    /// - the logs are always written to a file with infix `_rCURRENT`
89    /// - when the rotation criterion is fulfilled, it is closed and renamed to a file
90    ///   with another infix (see `Naming`),
91    ///   and then the logging continues again to the (fresh) file with infix `_rCURRENT`.
92    ///
93    /// Example:
94    ///
95    /// After some logging with your program `my_prog` and rotation with `Naming::Numbers`,
96    /// you will find files like
97    ///
98    /// ```text
99    /// my_prog_r00000.log
100    /// my_prog_r00001.log
101    /// my_prog_r00002.log
102    /// my_prog_rCURRENT.log
103    /// ```
104    ///
105    /// The cleanup parameter allows defining the strategy for dealing with older files.
106    /// See [`Cleanup`] for details.
107    #[must_use]
108    pub fn rotate(mut self, criterion: Criterion, naming: Naming, cleanup: Cleanup) -> Self {
109        self.o_rotation_config = Some(RotationConfig {
110            criterion,
111            naming,
112            cleanup,
113        });
114        self.file_spec.if_default_use_timestamp(false);
115        self
116    }
117
118    /// Set the file spec.
119    #[must_use]
120    pub(crate) fn file_spec(mut self, mut file_spec: FileSpec) -> Self {
121        if self.o_rotation_config.is_some() {
122            file_spec.if_default_use_timestamp(false);
123        }
124        self.file_spec = file_spec;
125        self
126    }
127
128    /// Makes the logger append to the given file, if it exists; by default, the file would be
129    /// truncated.
130    #[must_use]
131    pub fn append(mut self) -> Self {
132        self.cfg_append = true;
133        self
134    }
135
136    /// Set the maximum log level.
137    ///
138    /// The default is `log::LevelFilter::Trace`, i.e., all log levels are written.
139    #[must_use]
140    pub fn max_level(mut self, max_log_level: log::LevelFilter) -> Self {
141        self.max_log_level = max_log_level;
142        self
143    }
144
145    /// Enforces the use of UTC, rather than local time.
146    #[must_use]
147    pub fn use_utc(mut self) -> Self {
148        self.file_spec.use_utc = true;
149        self.use_utc = true;
150        self
151    }
152
153    /// The specified String will be used on unix systems to create in the current folder
154    /// a symbolic link to the current log file.
155    #[must_use]
156    pub fn create_symlink<P: Into<PathBuf>>(mut self, symlink: P) -> Self {
157        self.cfg_o_create_symlink = Some(symlink.into());
158        self
159    }
160
161    /// Use Windows line endings, rather than just `\n`.
162    #[must_use]
163    pub fn use_windows_line_ending(mut self) -> Self {
164        self.cfg_line_ending = super::WINDOWS_LINE_ENDING;
165        self
166    }
167
168    /// Sets the write mode for the `FileLogWriter`.
169    ///
170    /// See [`WriteMode`] for more (important!) details.
171    #[must_use]
172    pub fn write_mode(mut self, write_mode: WriteMode) -> Self {
173        self.cfg_write_mode = write_mode;
174        self
175    }
176
177    pub(crate) fn assert_write_mode(&self, write_mode: WriteMode) -> Result<(), FlexiLoggerError> {
178        if self.cfg_write_mode == write_mode {
179            Ok(())
180        } else {
181            Err(FlexiLoggerError::Reset)
182        }
183    }
184
185    #[must_use]
186    pub(crate) fn get_write_mode(&self) -> &WriteMode {
187        &self.cfg_write_mode
188    }
189
190    /// Produces the `FileLogWriter`.
191    ///
192    /// # Errors
193    ///
194    /// `FlexiLoggerError::Io` if the specified path doesn't work.
195    pub fn try_build(self) -> Result<FileLogWriter, FlexiLoggerError> {
196        Ok(FileLogWriter::new(
197            self.try_build_state()?,
198            self.max_log_level,
199            self.format,
200        ))
201    }
202
203    /// Produces the `FileLogWriter` and a handle that is connected with it.
204    ///
205    /// This allows handing out the `FileLogWriter` instance to methods that consume it, and still
206    /// be able to influence it via the handle.
207    ///
208    /// # Errors
209    ///
210    /// `FlexiLoggerError::Io` if the specified path doesn't work.
211    pub fn try_build_with_handle(
212        self,
213    ) -> Result<(ArcFileLogWriter, FileLogWriterHandle), FlexiLoggerError> {
214        Ok(ArcFileLogWriter::new_with_handle(FileLogWriter::new(
215            self.try_build_state()?,
216            self.max_log_level,
217            self.format,
218        )))
219    }
220
221    pub(super) fn try_build_state(&self) -> Result<State, FlexiLoggerError> {
222        // make sure the folder exists or create it
223        let dir = self.file_spec.get_directory();
224        let p_directory = Path::new(&dir);
225        std::fs::create_dir_all(p_directory)?;
226        if !std::fs::metadata(p_directory)?.is_dir() {
227            return Err(FlexiLoggerError::OutputBadDirectory);
228        };
229
230        #[cfg(feature = "async")]
231        let cleanup_in_background_thread = if let WriteMode::AsyncWith {
232            pool_capa: _,
233            message_capa: _,
234            flush_interval: _,
235        } = self.cfg_write_mode
236        {
237            false
238        } else {
239            self.cleanup_in_background_thread
240        };
241        #[cfg(not(feature = "async"))]
242        let cleanup_in_background_thread = self.cleanup_in_background_thread;
243
244        Ok(State::new(
245            FileLogWriterConfig {
246                print_message: self.cfg_print_message,
247                append: self.cfg_append,
248                line_ending: self.cfg_line_ending,
249                write_mode: self.cfg_write_mode,
250                file_spec: self.file_spec.clone(),
251                o_create_symlink: self.cfg_o_create_symlink.clone(),
252                use_utc: self.use_utc,
253            },
254            self.o_rotation_config.clone(),
255            cleanup_in_background_thread,
256        ))
257    }
258}
259
260/// Alternative set of methods to control the behavior of the `FileLogWriterBuilder`.
261/// Use these methods when you want to control the settings flexibly,
262/// e.g. with commandline arguments via `docopts` or `clap`.
263impl FileLogWriterBuilder {
264    /// With true, makes the `FileLogWriterBuilder` print an info message to stdout, each time
265    /// when a new file is used for log-output.
266    #[must_use]
267    pub fn o_print_message(mut self, print_message: bool) -> Self {
268        self.cfg_print_message = print_message;
269        self
270    }
271
272    /// By default, and with None, the log file will grow indefinitely.
273    /// If a `rotate_config` is set, when the log file reaches or exceeds the specified size,
274    /// the file will be closed and a new file will be opened.
275    /// Also the filename pattern changes: instead of the timestamp, a serial number
276    /// is included into the filename.
277    ///
278    /// The size is given in bytes, e.g. `o_rotate_over_size(Some(1_000))` will rotate
279    /// files once they reach a size of 1 kB.
280    ///
281    /// The cleanup strategy allows delimiting the used space on disk.
282    #[must_use]
283    pub fn o_rotate(mut self, rotate_config: Option<(Criterion, Naming, Cleanup)>) -> Self {
284        if let Some((criterion, naming, cleanup)) = rotate_config {
285            self.o_rotation_config = Some(RotationConfig {
286                criterion,
287                naming,
288                cleanup,
289            });
290            self.file_spec.if_default_use_timestamp(false);
291        } else {
292            self.o_rotation_config = None;
293            self.file_spec.if_default_use_timestamp(true);
294        }
295        self
296    }
297
298    /// If append is set to true, makes the logger append to the given file, if it exists.
299    /// By default, or with false, the file would be truncated.
300    #[must_use]
301    pub fn o_append(mut self, append: bool) -> Self {
302        self.cfg_append = append;
303        self
304    }
305
306    /// If a String is specified, it will be used on unix systems to create in the current folder
307    /// a symbolic link with this name to the current log file.
308    #[must_use]
309    pub fn o_create_symlink<S: Into<PathBuf>>(mut self, symlink: Option<S>) -> Self {
310        self.cfg_o_create_symlink = symlink.map(Into::into);
311        self
312    }
313}
314
315/// A shareable `FileLogWriter` with a handle.
316pub struct ArcFileLogWriter(Arc<FileLogWriter>);
317impl ArcFileLogWriter {
318    pub(crate) fn new_with_handle(flw: FileLogWriter) -> (Self, FileLogWriterHandle) {
319        let a_flw = Arc::new(flw);
320        (Self(Arc::clone(&a_flw)), FileLogWriterHandle(a_flw))
321    }
322}
323impl std::ops::Deref for ArcFileLogWriter {
324    type Target = FileLogWriter;
325    fn deref(&self) -> &FileLogWriter {
326        &(self.0)
327    }
328}
329impl Clone for ArcFileLogWriter {
330    fn clone(&self) -> Self {
331        Self(Arc::clone(&self.0))
332    }
333}
334impl std::io::Write for ArcFileLogWriter {
335    fn write(&mut self, buffer: &[u8]) -> std::result::Result<usize, std::io::Error> {
336        (*self.0).plain_write(buffer)
337    }
338    fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
339        LogWriter::flush(&*self.0)
340    }
341}
342
343/// Handle to the `FileLogWriter` in an `ArcFileLogWriter`
344/// that shuts down the `FileLogWriter` in its `Drop` implementation.
345pub struct FileLogWriterHandle(Arc<FileLogWriter>);
346impl Drop for FileLogWriterHandle {
347    fn drop(&mut self) {
348        self.0.shutdown();
349    }
350}