tari_log4rs/append/
file.rs1use derivative::Derivative;
6use log::Record;
7use parking_lot::Mutex;
8use std::{
9 fs::{self, File, OpenOptions},
10 io::{self, BufWriter, Write},
11 path::{Path, PathBuf},
12};
13
14#[cfg(feature = "config_parsing")]
15use crate::config::{Deserialize, Deserializers};
16#[cfg(feature = "config_parsing")]
17use crate::encode::EncoderConfig;
18
19use crate::{
20 append::{env_util::expand_env_vars, Append},
21 encode::{pattern::PatternEncoder, writer::simple::SimpleWriter, Encode},
22};
23
24#[cfg(feature = "config_parsing")]
26#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)]
27#[serde(deny_unknown_fields)]
28pub struct FileAppenderConfig {
29 path: String,
30 encoder: Option<EncoderConfig>,
31 append: Option<bool>,
32}
33
34#[derive(Derivative)]
36#[derivative(Debug)]
37pub struct FileAppender {
38 path: PathBuf,
39 #[derivative(Debug = "ignore")]
40 file: Mutex<SimpleWriter<BufWriter<File>>>,
41 encoder: Box<dyn Encode>,
42}
43
44impl Append for FileAppender {
45 fn append(&self, record: &Record) -> anyhow::Result<()> {
46 let mut file = self.file.lock();
47 self.encoder.encode(&mut *file, record)?;
48 file.flush()?;
49 Ok(())
50 }
51
52 fn flush(&self) {}
53}
54
55impl FileAppender {
56 pub fn builder() -> FileAppenderBuilder {
58 FileAppenderBuilder {
59 encoder: None,
60 append: true,
61 }
62 }
63}
64
65pub struct FileAppenderBuilder {
67 encoder: Option<Box<dyn Encode>>,
68 append: bool,
69}
70
71impl FileAppenderBuilder {
72 pub fn encoder(mut self, encoder: Box<dyn Encode>) -> FileAppenderBuilder {
74 self.encoder = Some(encoder);
75 self
76 }
77
78 pub fn append(mut self, append: bool) -> FileAppenderBuilder {
82 self.append = append;
83 self
84 }
85
86 pub fn build<P: AsRef<Path>>(self, path: P) -> io::Result<FileAppender> {
92 let path_cow = path.as_ref().to_string_lossy();
93 let path: PathBuf = expand_env_vars(path_cow).as_ref().into();
94 if let Some(parent) = path.parent() {
95 fs::create_dir_all(parent)?;
96 }
97 let file = OpenOptions::new()
98 .write(true)
99 .append(self.append)
100 .truncate(!self.append)
101 .create(true)
102 .open(&path)?;
103
104 Ok(FileAppender {
105 path,
106 file: Mutex::new(SimpleWriter(BufWriter::with_capacity(1024, file))),
107 encoder: self
108 .encoder
109 .unwrap_or_else(|| Box::new(PatternEncoder::default())),
110 })
111 }
112}
113
114#[cfg(feature = "config_parsing")]
137#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
138pub struct FileAppenderDeserializer;
139
140#[cfg(feature = "config_parsing")]
141impl Deserialize for FileAppenderDeserializer {
142 type Trait = dyn Append;
143
144 type Config = FileAppenderConfig;
145
146 fn deserialize(
147 &self,
148 config: FileAppenderConfig,
149 deserializers: &Deserializers,
150 ) -> anyhow::Result<Box<Self::Trait>> {
151 let mut appender = FileAppender::builder();
152 if let Some(append) = config.append {
153 appender = appender.append(append);
154 }
155 if let Some(encoder) = config.encoder {
156 appender = appender.encoder(deserializers.deserialize(&encoder.kind, encoder.config)?);
157 }
158 Ok(Box::new(appender.build(&config.path)?))
159 }
160}
161
162#[cfg(test)]
163mod test {
164 use super::*;
165
166 #[test]
167 fn create_directories() {
168 let tempdir = tempfile::tempdir().unwrap();
169
170 FileAppender::builder()
171 .build(tempdir.path().join("foo").join("bar").join("baz.log"))
172 .unwrap();
173 }
174
175 #[test]
176 fn append_false() {
177 let tempdir = tempfile::tempdir().unwrap();
178 FileAppender::builder()
179 .append(false)
180 .build(tempdir.path().join("foo.log"))
181 .unwrap();
182 }
183}