tari_log4rs/append/rolling_file/
mod.rs1use derivative::Derivative;
20use log::Record;
21use parking_lot::Mutex;
22use std::{
23 fs::{self, File, OpenOptions},
24 io::{self, BufWriter, Write},
25 path::{Path, PathBuf},
26};
27
28#[cfg(feature = "config_parsing")]
29use serde_value::Value;
30#[cfg(feature = "config_parsing")]
31use std::collections::BTreeMap;
32
33use crate::{
34 append::Append,
35 encode::{self, pattern::PatternEncoder, Encode},
36};
37
38#[cfg(feature = "config_parsing")]
39use crate::config::{Deserialize, Deserializers};
40#[cfg(feature = "config_parsing")]
41use crate::encode::EncoderConfig;
42
43pub mod policy;
44
45#[cfg(feature = "config_parsing")]
47#[derive(Clone, Eq, PartialEq, Hash, Debug, serde::Deserialize)]
48#[serde(deny_unknown_fields)]
49pub struct RollingFileAppenderConfig {
50 path: String,
51 append: Option<bool>,
52 encoder: Option<EncoderConfig>,
53 policy: Policy,
54}
55
56#[cfg(feature = "config_parsing")]
57#[derive(Clone, Eq, PartialEq, Hash, Debug)]
58struct Policy {
59 kind: String,
60 config: Value,
61}
62
63#[cfg(feature = "config_parsing")]
64impl<'de> serde::Deserialize<'de> for Policy {
65 fn deserialize<D>(d: D) -> Result<Policy, D::Error>
66 where
67 D: serde::Deserializer<'de>,
68 {
69 let mut map = BTreeMap::<Value, Value>::deserialize(d)?;
70
71 let kind = match map.remove(&Value::String("kind".to_owned())) {
72 Some(kind) => kind.deserialize_into().map_err(|e| e.to_error())?,
73 None => "compound".to_owned(),
74 };
75
76 Ok(Policy {
77 kind,
78 config: Value::Map(map),
79 })
80 }
81}
82
83#[derive(Debug)]
84struct LogWriter {
85 file: BufWriter<File>,
86 len: u64,
87}
88
89impl io::Write for LogWriter {
90 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
91 self.file.write(buf).map(|n| {
92 self.len += n as u64;
93 n
94 })
95 }
96
97 fn flush(&mut self) -> io::Result<()> {
98 self.file.flush()
99 }
100}
101
102impl encode::Write for LogWriter {}
103
104#[derive(Debug)]
106pub struct LogFile<'a> {
107 writer: &'a mut Option<LogWriter>,
108 path: &'a Path,
109 len: u64,
110}
111
112#[allow(clippy::len_without_is_empty)]
113impl<'a> LogFile<'a> {
114 pub fn path(&self) -> &Path {
116 self.path
117 }
118
119 #[deprecated(since = "0.9.1", note = "Please use the len_estimate function instead")]
126 pub fn len(&self) -> u64 {
127 self.len
128 }
129
130 pub fn len_estimate(&self) -> u64 {
137 self.len
138 }
139
140 pub fn roll(&mut self) {
149 *self.writer = None;
150 }
151}
152
153#[derive(Derivative)]
155#[derivative(Debug)]
156pub struct RollingFileAppender {
157 #[derivative(Debug = "ignore")]
158 writer: Mutex<Option<LogWriter>>,
159 path: PathBuf,
160 append: bool,
161 encoder: Box<dyn Encode>,
162 policy: Box<dyn policy::Policy>,
163}
164
165impl Append for RollingFileAppender {
166 fn append(&self, record: &Record) -> anyhow::Result<()> {
167 let mut writer = self.writer.lock();
169
170 let len = {
171 let writer = self.get_writer(&mut writer)?;
172 self.encoder.encode(writer, record)?;
173 writer.flush()?;
174 writer.len
175 };
176
177 let mut file = LogFile {
178 writer: &mut writer,
179 path: &self.path,
180 len,
181 };
182
183 self.policy.process(&mut file)
186 }
187
188 fn flush(&self) {}
189}
190
191impl RollingFileAppender {
192 pub fn builder() -> RollingFileAppenderBuilder {
194 RollingFileAppenderBuilder {
195 append: true,
196 encoder: None,
197 }
198 }
199
200 fn get_writer<'a>(&self, writer: &'a mut Option<LogWriter>) -> io::Result<&'a mut LogWriter> {
201 if writer.is_none() {
202 let file = OpenOptions::new()
203 .write(true)
204 .append(self.append)
205 .truncate(!self.append)
206 .create(true)
207 .open(&self.path)?;
208 let len = if self.append {
209 file.metadata()?.len()
210 } else {
211 0
212 };
213 *writer = Some(LogWriter {
214 file: BufWriter::with_capacity(1024, file),
215 len,
216 });
217 }
218
219 Ok(writer.as_mut().unwrap())
221 }
222}
223
224pub struct RollingFileAppenderBuilder {
226 append: bool,
227 encoder: Option<Box<dyn Encode>>,
228}
229
230impl RollingFileAppenderBuilder {
231 pub fn append(mut self, append: bool) -> RollingFileAppenderBuilder {
235 self.append = append;
236 self
237 }
238
239 pub fn encoder(mut self, encoder: Box<dyn Encode>) -> RollingFileAppenderBuilder {
243 self.encoder = Some(encoder);
244 self
245 }
246
247 pub fn build<P>(
253 self,
254 path: P,
255 policy: Box<dyn policy::Policy>,
256 ) -> io::Result<RollingFileAppender>
257 where
258 P: AsRef<Path>,
259 {
260 let path = super::env_util::expand_env_vars(path.as_ref().to_string_lossy());
261 let appender = RollingFileAppender {
262 writer: Mutex::new(None),
263 path: path.as_ref().into(),
264 append: self.append,
265 encoder: self
266 .encoder
267 .unwrap_or_else(|| Box::new(PatternEncoder::default())),
268 policy,
269 };
270
271 if let Some(parent) = appender.path.parent() {
272 fs::create_dir_all(parent)?;
273 }
274
275 appender.get_writer(&mut appender.writer.lock())?;
277
278 Ok(appender)
279 }
280}
281
282#[cfg(feature = "config_parsing")]
320#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
321pub struct RollingFileAppenderDeserializer;
322
323#[cfg(feature = "config_parsing")]
324impl Deserialize for RollingFileAppenderDeserializer {
325 type Trait = dyn Append;
326
327 type Config = RollingFileAppenderConfig;
328
329 fn deserialize(
330 &self,
331 config: RollingFileAppenderConfig,
332 deserializers: &Deserializers,
333 ) -> anyhow::Result<Box<dyn Append>> {
334 let mut builder = RollingFileAppender::builder();
335 if let Some(append) = config.append {
336 builder = builder.append(append);
337 }
338 if let Some(encoder) = config.encoder {
339 let encoder = deserializers.deserialize(&encoder.kind, encoder.config)?;
340 builder = builder.encoder(encoder);
341 }
342
343 let policy = deserializers.deserialize(&config.policy.kind, config.policy.config)?;
344 let appender = builder.build(config.path, policy)?;
345 Ok(Box::new(appender))
346 }
347}
348
349#[cfg(test)]
350mod test {
351 use std::{
352 fs::File,
353 io::{Read, Write},
354 };
355
356 use super::*;
357 use crate::append::rolling_file::policy::Policy;
358
359 #[test]
360 #[cfg(feature = "yaml_format")]
361 fn deserialize() {
362 use crate::config::{Deserializers, RawConfig};
363
364 let dir = tempfile::tempdir().unwrap();
365
366 let config = format!(
367 "
368appenders:
369 foo:
370 kind: rolling_file
371 path: {0}/foo.log
372 policy:
373 trigger:
374 kind: size
375 limit: 1024
376 roller:
377 kind: delete
378 bar:
379 kind: rolling_file
380 path: {0}/foo.log
381 policy:
382 kind: compound
383 trigger:
384 kind: size
385 limit: 5 mb
386 roller:
387 kind: fixed_window
388 pattern: '{0}/foo.log.{{}}'
389 base: 1
390 count: 5
391",
392 dir.path().display()
393 );
394
395 let config = ::serde_yaml::from_str::<RawConfig>(&config).unwrap();
396 let errors = config.appenders_lossy(&Deserializers::new()).1;
397 println!("{:?}", errors);
398 assert!(errors.is_empty());
399 }
400
401 #[derive(Debug)]
402 struct NopPolicy;
403
404 impl Policy for NopPolicy {
405 fn process(&self, _: &mut LogFile) -> anyhow::Result<()> {
406 Ok(())
407 }
408 }
409
410 #[test]
411 fn append() {
412 let dir = tempfile::tempdir().unwrap();
413 let path = dir.path().join("append.log");
414 RollingFileAppender::builder()
415 .append(true)
416 .build(&path, Box::new(NopPolicy))
417 .unwrap();
418 assert!(path.exists());
419 File::create(&path).unwrap().write_all(b"hello").unwrap();
420
421 RollingFileAppender::builder()
422 .append(true)
423 .build(&path, Box::new(NopPolicy))
424 .unwrap();
425 let mut contents = vec![];
426 File::open(&path)
427 .unwrap()
428 .read_to_end(&mut contents)
429 .unwrap();
430 assert_eq!(contents, b"hello");
431 }
432
433 #[test]
434 fn truncate() {
435 let dir = tempfile::tempdir().unwrap();
436 let path = dir.path().join("truncate.log");
437 RollingFileAppender::builder()
438 .append(false)
439 .build(&path, Box::new(NopPolicy))
440 .unwrap();
441 assert!(path.exists());
442 File::create(&path).unwrap().write_all(b"hello").unwrap();
443
444 RollingFileAppender::builder()
445 .append(false)
446 .build(&path, Box::new(NopPolicy))
447 .unwrap();
448 let mut contents = vec![];
449 File::open(&path)
450 .unwrap()
451 .read_to_end(&mut contents)
452 .unwrap();
453 assert_eq!(contents, b"");
454 }
455}