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