tracing_appender/rolling/
builder.rs

1use super::{RollingFileAppender, Rotation};
2use std::{io, path::Path};
3use thiserror::Error;
4
5/// A [builder] for configuring [`RollingFileAppender`]s.
6///
7/// [builder]: https://rust-unofficial.github.io/patterns/patterns/creational/builder.html
8#[derive(Debug)]
9pub struct Builder {
10    pub(super) rotation: Rotation,
11    pub(super) prefix: Option<String>,
12    pub(super) suffix: Option<String>,
13    pub(super) max_files: Option<usize>,
14}
15
16/// Errors returned by [`Builder::build`].
17#[derive(Error, Debug)]
18#[error("{context}: {source}")]
19pub struct InitError {
20    context: &'static str,
21    #[source]
22    source: io::Error,
23}
24
25impl InitError {
26    pub(crate) fn ctx(context: &'static str) -> impl FnOnce(io::Error) -> Self {
27        move |source| Self { context, source }
28    }
29}
30
31impl Builder {
32    /// Returns a new `Builder` for configuring a [`RollingFileAppender`], with
33    /// the default parameters.
34    ///
35    /// # Default Values
36    ///
37    /// The default values for the builder are:
38    ///
39    /// | Parameter | Default Value | Notes |
40    /// | :-------- | :------------ | :---- |
41    /// | [`rotation`] | [`Rotation::NEVER`] | By default, log files will never be rotated. |
42    /// | [`filename_prefix`] | `""` | By default, log file names will not have a prefix. |
43    /// | [`filename_suffix`] | `""` | By default, log file names will not have a suffix. |
44    /// | [`max_log_files`] | `None` | By default, there is no limit for maximum log file count. |
45    ///
46    /// [`rotation`]: Self::rotation
47    /// [`filename_prefix`]: Self::filename_prefix
48    /// [`filename_suffix`]: Self::filename_suffix
49    /// [`max_log_files`]: Self::max_log_files
50    #[must_use]
51    pub const fn new() -> Self {
52        Self {
53            rotation: Rotation::NEVER,
54            prefix: None,
55            suffix: None,
56            max_files: None,
57        }
58    }
59
60    /// Sets the [rotation strategy] for log files.
61    ///
62    /// By default, this is [`Rotation::NEVER`].
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// # fn docs() {
68    /// use tracing_appender::rolling::{Rotation, RollingFileAppender};
69    ///
70    /// let appender = RollingFileAppender::builder()
71    ///     .rotation(Rotation::HOURLY) // rotate log files once every hour
72    ///     // ...
73    ///     .build("/var/log")
74    ///     .expect("failed to initialize rolling file appender");
75    ///
76    /// # drop(appender)
77    /// # }
78    /// ```
79    ///
80    /// [rotation strategy]: Rotation
81    #[must_use]
82    pub fn rotation(self, rotation: Rotation) -> Self {
83        Self { rotation, ..self }
84    }
85
86    /// Sets the prefix for log filenames. The prefix is output before the
87    /// timestamp in the file name, and if it is non-empty, it is followed by a
88    /// dot (`.`).
89    ///
90    /// By default, log files do not have a prefix.
91    ///
92    /// # Examples
93    ///
94    /// Setting a prefix:
95    ///
96    /// ```
97    /// use tracing_appender::rolling::RollingFileAppender;
98    ///
99    /// # fn docs() {
100    /// let appender = RollingFileAppender::builder()
101    ///     .filename_prefix("myapp.log") // log files will have names like "myapp.log.2019-01-01"
102    ///     // ...
103    ///     .build("/var/log")
104    ///     .expect("failed to initialize rolling file appender");
105    /// # drop(appender)
106    /// # }
107    /// ```
108    ///
109    /// No prefix:
110    ///
111    /// ```
112    /// use tracing_appender::rolling::RollingFileAppender;
113    ///
114    /// # fn docs() {
115    /// let appender = RollingFileAppender::builder()
116    ///     .filename_prefix("") // log files will have names like "2019-01-01"
117    ///     // ...
118    ///     .build("/var/log")
119    ///     .expect("failed to initialize rolling file appender");
120    /// # drop(appender)
121    /// # }
122    /// ```
123    ///
124    /// [rotation strategy]: Rotation
125    #[must_use]
126    pub fn filename_prefix(self, prefix: impl Into<String>) -> Self {
127        let prefix = prefix.into();
128        // If the configured prefix is the empty string, then don't include a
129        // separator character.
130        let prefix = if prefix.is_empty() {
131            None
132        } else {
133            Some(prefix)
134        };
135        Self { prefix, ..self }
136    }
137
138    /// Sets the suffix for log filenames. The suffix is output after the
139    /// timestamp in the file name, and if it is non-empty, it is preceded by a
140    /// dot (`.`).
141    ///
142    /// By default, log files do not have a suffix.
143    ///
144    /// # Examples
145    ///
146    /// Setting a suffix:
147    ///
148    /// ```
149    /// use tracing_appender::rolling::RollingFileAppender;
150    ///
151    /// # fn docs() {
152    /// let appender = RollingFileAppender::builder()
153    ///     .filename_suffix("myapp.log") // log files will have names like "2019-01-01.myapp.log"
154    ///     // ...
155    ///     .build("/var/log")
156    ///     .expect("failed to initialize rolling file appender");
157    /// # drop(appender)
158    /// # }
159    /// ```
160    ///
161    /// No suffix:
162    ///
163    /// ```
164    /// use tracing_appender::rolling::RollingFileAppender;
165    ///
166    /// # fn docs() {
167    /// let appender = RollingFileAppender::builder()
168    ///     .filename_suffix("") // log files will have names like "2019-01-01"
169    ///     // ...
170    ///     .build("/var/log")
171    ///     .expect("failed to initialize rolling file appender");
172    /// # drop(appender)
173    /// # }
174    /// ```
175    ///
176    /// [rotation strategy]: Rotation
177    #[must_use]
178    pub fn filename_suffix(self, suffix: impl Into<String>) -> Self {
179        let suffix = suffix.into();
180        // If the configured suffix is the empty string, then don't include a
181        // separator character.
182        let suffix = if suffix.is_empty() {
183            None
184        } else {
185            Some(suffix)
186        };
187        Self { suffix, ..self }
188    }
189
190    /// Keeps the last `n` log files on disk.
191    ///
192    /// When a new log file is created, if there are `n` or more
193    /// existing log files in the directory, the oldest will be deleted.
194    /// If no value is supplied, the `RollingAppender` will not remove any files.
195    ///
196    /// Files are considered candidates for deletion based on the following
197    /// criteria:
198    ///
199    /// * The file must not be a directory or symbolic link.
200    /// * If the appender is configured with a [`filename_prefix`], the file
201    ///   name must start with that prefix.
202    /// * If the appender is configured with a [`filename_suffix`], the file
203    ///   name must end with that suffix.
204    /// * If the appender has neither a filename prefix nor a suffix, then the
205    ///   file name must parse as a valid date based on the appender's date
206    ///   format.
207    ///
208    /// Files matching these criteria may be deleted if the maximum number of
209    /// log files in the directory has been reached.
210    ///
211    /// [`filename_prefix`]: Self::filename_prefix
212    /// [`filename_suffix`]: Self::filename_suffix
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// use tracing_appender::rolling::RollingFileAppender;
218    ///
219    /// # fn docs() {
220    /// let appender = RollingFileAppender::builder()
221    ///     .max_log_files(5) // only the most recent 5 log files will be kept
222    ///     // ...
223    ///     .build("/var/log")
224    ///     .expect("failed to initialize rolling file appender");
225    /// # drop(appender)
226    /// # }
227    /// ```
228    #[must_use]
229    pub fn max_log_files(self, n: usize) -> Self {
230        Self {
231            max_files: Some(n),
232            ..self
233        }
234    }
235
236    /// Builds a new [`RollingFileAppender`] with the configured parameters,
237    /// emitting log files to the provided directory.
238    ///
239    /// Unlike [`RollingFileAppender::new`], this returns a `Result` rather than
240    /// panicking when the appender cannot be initialized.
241    ///
242    /// # Examples
243    ///
244    /// ```
245    /// use tracing_appender::rolling::{Rotation, RollingFileAppender};
246    ///
247    /// # fn docs() {
248    /// let appender = RollingFileAppender::builder()
249    ///     .rotation(Rotation::DAILY) // rotate log files once per day
250    ///     .filename_prefix("myapp.log") // log files will have names like "myapp.log.2019-01-01"
251    ///     .build("/var/log/myapp") // write log files to the '/var/log/myapp' directory
252    ///     .expect("failed to initialize rolling file appender");
253    /// # drop(appender);
254    /// # }
255    /// ```
256    ///
257    /// This is equivalent to
258    /// ```
259    /// # fn docs() {
260    /// let appender = tracing_appender::rolling::daily("myapp.log", "/var/log/myapp");
261    /// # drop(appender);
262    /// # }
263    /// ```
264    pub fn build(&self, directory: impl AsRef<Path>) -> Result<RollingFileAppender, InitError> {
265        RollingFileAppender::from_builder(self, directory)
266    }
267}
268
269impl Default for Builder {
270    fn default() -> Self {
271        Self::new()
272    }
273}