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}