• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use super::{RollingFileAppender, Rotation};
2 use std::{io, path::Path};
3 use 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)]
9 pub 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}")]
19 pub struct InitError {
20     context: &'static str,
21     #[source]
22     source: io::Error,
23 }
24 
25 impl InitError {
ctx(context: &'static str) -> impl FnOnce(io::Error) -> Self26     pub(crate) fn ctx(context: &'static str) -> impl FnOnce(io::Error) -> Self {
27         move |source| Self { context, source }
28     }
29 }
30 
31 impl 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]
new() -> Self51     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]
rotation(self, rotation: Rotation) -> Self82     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]
filename_prefix(self, prefix: impl Into<String>) -> Self126     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]
filename_suffix(self, suffix: impl Into<String>) -> Self178     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]
max_log_files(self, n: usize) -> Self229     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     /// ```
build(&self, directory: impl AsRef<Path>) -> Result<RollingFileAppender, InitError>264     pub fn build(&self, directory: impl AsRef<Path>) -> Result<RollingFileAppender, InitError> {
265         RollingFileAppender::from_builder(self, directory)
266     }
267 }
268 
269 impl Default for Builder {
default() -> Self270     fn default() -> Self {
271         Self::new()
272     }
273 }
274