tari_log4rs/config/
file.rs1use std::{
2 fs,
3 path::{Path, PathBuf},
4 thread,
5 time::{Duration, SystemTime},
6};
7
8use thiserror::Error;
9
10use super::{init_config, Config, Deserializers, Handle, RawConfig};
11use crate::handle_error;
12
13pub fn init_file<P>(path: P, deserializers: Deserializers) -> anyhow::Result<()>
23where
24 P: AsRef<Path>,
25{
26 let path = path.as_ref().to_path_buf();
27 let format = Format::from_path(&path)?;
28 let source = read_config(&path)?;
29 let modified = fs::metadata(&path).and_then(|m| m.modified()).ok();
31 let config = format.parse(&source)?;
32
33 let refresh_rate = config.refresh_rate();
34 let config = deserialize(&config, &deserializers);
35
36 match init_config(config) {
37 Ok(handle) => {
38 if let Some(refresh_rate) = refresh_rate {
39 ConfigReloader::start(
40 path,
41 format,
42 refresh_rate,
43 source,
44 modified,
45 deserializers,
46 handle,
47 );
48 }
49 Ok(())
50 }
51 Err(e) => Err(e.into()),
52 }
53}
54
55pub fn load_config_file<P>(path: P, deserializers: Deserializers) -> anyhow::Result<Config>
60where
61 P: AsRef<Path>,
62{
63 let path = path.as_ref();
64 let format = Format::from_path(path)?;
65 let source = read_config(path)?;
66 let config = format.parse(&source)?;
67
68 Ok(deserialize(&config, &deserializers))
69}
70
71#[derive(Debug, Error)]
73pub enum FormatError {
74 #[error("the `yaml_format` feature is required for YAML support")]
76 YamlFeatureFlagRequired,
77
78 #[error("the `json_format` feature is required for JSON support")]
80 JsonFeatureFlagRequired,
81
82 #[error("the `toml_format` feature is required for TOML support")]
84 TomlFeatureFlagRequired,
85
86 #[error("unsupported file format `{0}`")]
88 UnsupportedFormat(String),
89
90 #[error("unable to determine the file format")]
92 UnknownFormat,
93}
94
95#[derive(Debug)]
96enum Format {
97 #[cfg(feature = "yaml_format")]
98 Yaml,
99 #[cfg(feature = "json_format")]
100 Json,
101 #[cfg(feature = "toml_format")]
102 Toml,
103}
104
105impl Format {
106 fn from_path(path: &Path) -> anyhow::Result<Format> {
107 match path.extension().and_then(|s| s.to_str()) {
108 #[cfg(feature = "yaml_format")]
109 Some("yaml") | Some("yml") => Ok(Format::Yaml),
110 #[cfg(not(feature = "yaml_format"))]
111 Some("yaml") | Some("yml") => Err(FormatError::YamlFeatureFlagRequired.into()),
112
113 #[cfg(feature = "json_format")]
114 Some("json") => Ok(Format::Json),
115 #[cfg(not(feature = "json_format"))]
116 Some("json") => Err(FormatError::JsonFeatureFlagRequired.into()),
117
118 #[cfg(feature = "toml_format")]
119 Some("toml") => Ok(Format::Toml),
120 #[cfg(not(feature = "toml_format"))]
121 Some("toml") => Err(FormatError::TomlFeatureFlagRequired.into()),
122
123 Some(f) => Err(FormatError::UnsupportedFormat(f.to_string()).into()),
124 None => Err(FormatError::UnknownFormat.into()),
125 }
126 }
127
128 #[allow(unused_variables)]
129 fn parse(&self, source: &str) -> anyhow::Result<RawConfig> {
130 match *self {
131 #[cfg(feature = "yaml_format")]
132 Format::Yaml => ::serde_yaml::from_str(source).map_err(Into::into),
133 #[cfg(feature = "json_format")]
134 Format::Json => ::serde_json::from_str(source).map_err(Into::into),
135 #[cfg(feature = "toml_format")]
136 Format::Toml => ::toml::from_str(source).map_err(Into::into),
137 }
138 }
139}
140
141fn read_config(path: &Path) -> anyhow::Result<String> {
142 let s = fs::read_to_string(path)?;
143 Ok(s)
144}
145
146fn deserialize(config: &RawConfig, deserializers: &Deserializers) -> Config {
147 let (appenders, mut errors) = config.appenders_lossy(deserializers);
148 errors.handle();
149
150 let (config, mut errors) = Config::builder()
151 .appenders(appenders)
152 .loggers(config.loggers())
153 .build_lossy(config.root());
154
155 errors.handle();
156
157 config
158}
159
160struct ConfigReloader {
161 path: PathBuf,
162 format: Format,
163 source: String,
164 modified: Option<SystemTime>,
165 deserializers: Deserializers,
166 handle: Handle,
167}
168
169impl ConfigReloader {
170 fn start(
171 path: PathBuf,
172 format: Format,
173 rate: Duration,
174 source: String,
175 modified: Option<SystemTime>,
176 deserializers: Deserializers,
177 handle: Handle,
178 ) {
179 let mut reloader = ConfigReloader {
180 path,
181 format,
182 source,
183 modified,
184 deserializers,
185 handle,
186 };
187
188 thread::Builder::new()
189 .name("log4rs refresh".to_owned())
190 .spawn(move || reloader.run(rate))
191 .unwrap();
192 }
193
194 fn run(&mut self, mut rate: Duration) {
195 loop {
196 thread::sleep(rate);
197
198 match self.run_once(rate) {
199 Ok(Some(r)) => rate = r,
200 Ok(None) => break,
201 Err(e) => handle_error(&e),
202 }
203 }
204 }
205
206 fn run_once(&mut self, rate: Duration) -> anyhow::Result<Option<Duration>> {
207 if let Some(last_modified) = self.modified {
208 let modified = fs::metadata(&self.path).and_then(|m| m.modified())?;
209 if last_modified == modified {
210 return Ok(Some(rate));
211 }
212
213 self.modified = Some(modified);
214 }
215
216 let source = read_config(&self.path)?;
217
218 if source == self.source {
219 return Ok(Some(rate));
220 }
221
222 self.source = source;
223
224 let config = self.format.parse(&self.source)?;
225 let rate = config.refresh_rate();
226 let config = deserialize(&config, &self.deserializers);
227
228 self.handle.set_config(config);
229
230 Ok(rate)
231 }
232}