1use crate::{FormatFlavor, Verbosity};
2use serde::de::{MapAccess, Visitor};
3use serde::{Deserialize, Deserializer};
4use std::collections::BTreeMap;
5use std::fmt::Formatter;
6use strut_factory::impl_deserialize_field;
7
8pub mod flavor;
9pub mod verbosity;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct TracingConfig {
17 verbosity: Verbosity,
18 flavor: FormatFlavor,
19 color: bool,
20 show_timestamp: bool,
21 show_target: bool,
22 show_file: bool,
23 show_line_number: bool,
24 show_level: bool,
25 show_thread_id: bool,
26 show_thread_name: bool,
27 #[cfg(feature = "json")]
28 flatten_json: bool,
29 targets: BTreeMap<String, Verbosity>,
30}
31
32impl TracingConfig {
33 pub fn with_target(
35 mut self,
36 target: impl Into<String>,
37 verbosity: impl Into<Verbosity>,
38 ) -> Self {
39 self.targets.insert(target.into(), verbosity.into());
40
41 self
42 }
43
44 pub fn with_targets<T, L>(mut self, targets: impl IntoIterator<Item = (T, L)>) -> Self
46 where
47 T: Into<String>,
48 L: Into<Verbosity>,
49 {
50 for (target, verbosity) in targets.into_iter() {
51 self.targets.insert(target.into(), verbosity.into());
52 }
53
54 self
55 }
56}
57
58impl TracingConfig {
59 pub fn verbosity(&self) -> Verbosity {
62 self.verbosity
63 }
64
65 pub fn flavor(&self) -> FormatFlavor {
68 self.flavor
69 }
70
71 pub fn color(&self) -> bool {
74 self.color
75 }
76
77 pub fn show_timestamp(&self) -> bool {
80 self.show_timestamp
81 }
82
83 pub fn show_target(&self) -> bool {
86 self.show_target
87 }
88
89 pub fn show_file(&self) -> bool {
92 self.show_file
93 }
94
95 pub fn show_line_number(&self) -> bool {
99 self.show_line_number
100 }
101
102 pub fn show_level(&self) -> bool {
105 self.show_level
106 }
107
108 pub fn show_thread_id(&self) -> bool {
112 self.show_thread_id
113 }
114
115 pub fn show_thread_name(&self) -> bool {
119 self.show_thread_name
120 }
121
122 #[cfg(feature = "json")]
124 pub fn flatten_json(&self) -> bool {
125 self.flatten_json
126 }
127
128 pub fn targets(&self) -> &BTreeMap<String, Verbosity> {
133 &self.targets
134 }
135}
136
137impl Default for TracingConfig {
138 fn default() -> Self {
139 Self {
140 verbosity: Verbosity::default(),
141 flavor: FormatFlavor::default(),
142 color: Self::default_color(),
143 show_timestamp: Self::default_show_timestamp(),
144 show_target: Self::default_show_target(),
145 show_file: Self::default_show_file(),
146 show_line_number: Self::default_show_line_number(),
147 show_level: Self::default_show_level(),
148 show_thread_id: Self::default_show_thread_id(),
149 show_thread_name: Self::default_show_thread_name(),
150 #[cfg(feature = "json")]
151 flatten_json: Self::default_flatten_json(),
152 targets: BTreeMap::default(),
153 }
154 }
155}
156
157impl TracingConfig {
158 fn default_color() -> bool {
159 true
160 }
161
162 fn default_show_timestamp() -> bool {
163 true
164 }
165
166 fn default_show_target() -> bool {
167 true
168 }
169
170 fn default_show_file() -> bool {
171 false
172 }
173
174 fn default_show_line_number() -> bool {
175 false
176 }
177
178 fn default_show_level() -> bool {
179 true
180 }
181
182 fn default_show_thread_id() -> bool {
183 true
184 }
185
186 fn default_show_thread_name() -> bool {
187 false
188 }
189
190 #[cfg(feature = "json")]
191 fn default_flatten_json() -> bool {
192 true
193 }
194}
195
196impl AsRef<TracingConfig> for TracingConfig {
197 fn as_ref(&self) -> &TracingConfig {
198 self
199 }
200}
201
202const _: () = {
203 impl<'de> Deserialize<'de> for TracingConfig {
204 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
205 where
206 D: Deserializer<'de>,
207 {
208 deserializer.deserialize_map(TracingConfigVisitor)
209 }
210 }
211
212 struct TracingConfigVisitor;
213
214 impl<'de> Visitor<'de> for TracingConfigVisitor {
215 type Value = TracingConfig;
216
217 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
218 formatter.write_str("a map of tracing (logging) configuration")
219 }
220
221 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
222 where
223 A: MapAccess<'de>,
224 {
225 let mut level = None;
226 let mut flavor = None;
227 let mut color = None;
228 let mut show_timestamp = None;
229 let mut show_target = None;
230 let mut show_file = None;
231 let mut show_line_number = None;
232 let mut show_level = None;
233 let mut show_thread_id = None;
234 let mut show_thread_name = None;
235 #[cfg(feature = "json")]
236 let mut flatten_json = None;
237 let mut targets = None;
238
239 while let Some(key) = map.next_key()? {
240 match key {
241 TracingConfigField::verbosity => key.poll(&mut map, &mut level)?,
242 TracingConfigField::flavor => key.poll(&mut map, &mut flavor)?,
243 TracingConfigField::color => key.poll(&mut map, &mut color)?,
244 TracingConfigField::show_timestamp => {
245 key.poll(&mut map, &mut show_timestamp)?
246 }
247 TracingConfigField::show_target => key.poll(&mut map, &mut show_target)?,
248 TracingConfigField::show_file => key.poll(&mut map, &mut show_file)?,
249 TracingConfigField::show_line_number => {
250 key.poll(&mut map, &mut show_line_number)?
251 }
252 TracingConfigField::show_level => key.poll(&mut map, &mut show_level)?,
253 TracingConfigField::show_thread_id => {
254 key.poll(&mut map, &mut show_thread_id)?
255 }
256 TracingConfigField::show_thread_name => {
257 key.poll(&mut map, &mut show_thread_name)?
258 }
259 #[cfg(feature = "json")]
260 TracingConfigField::flatten_json => key.poll(&mut map, &mut flatten_json)?,
261 #[cfg(not(feature = "json"))]
262 TracingConfigField::flatten_json => map.next_value()?,
263 TracingConfigField::targets => key.poll(&mut map, &mut targets)?,
264 TracingConfigField::__ignore => map.next_value()?,
265 };
266 }
267
268 Ok(TracingConfig {
269 verbosity: level.unwrap_or_default(),
270 flavor: flavor.unwrap_or_default(),
271 color: color.unwrap_or_else(TracingConfig::default_color),
272 show_timestamp: show_timestamp
273 .unwrap_or_else(TracingConfig::default_show_timestamp),
274 show_target: show_target.unwrap_or_else(TracingConfig::default_show_target),
275 show_file: show_file.unwrap_or_else(TracingConfig::default_show_file),
276 show_line_number: show_line_number
277 .unwrap_or_else(TracingConfig::default_show_line_number),
278 show_level: show_level.unwrap_or_else(TracingConfig::default_show_level),
279 show_thread_id: show_thread_id
280 .unwrap_or_else(TracingConfig::default_show_thread_id),
281 show_thread_name: show_thread_name
282 .unwrap_or_else(TracingConfig::default_show_thread_name),
283 #[cfg(feature = "json")]
284 flatten_json: flatten_json.unwrap_or_else(TracingConfig::default_flatten_json),
285 targets: targets.unwrap_or_default(),
286 })
287 }
288 }
289
290 impl_deserialize_field!(
291 TracingConfigField,
292 strut_deserialize::Slug::eq_as_slugs,
293 verbosity | level,
294 flavor | flavour,
295 color
296 | with_color
297 | colour
298 | with_colour
299 | show_color
300 | show_colour
301 | show_colors
302 | show_colours,
303 show_timestamp | with_timestamp,
304 show_target | with_target,
305 show_file | with_file,
306 show_line_number | show_line | with_line | with_line_number,
307 show_level | with_level,
308 show_thread_id | with_thread_id,
309 show_thread_name | with_thread_name,
310 flatten_json | flat_json | with_flat_json,
311 targets | custom_targets | target_verbosity,
312 );
313};
314
315#[cfg(test)]
316mod tests {
317 use crate::{FormatFlavor, TracingConfig, Verbosity};
318 use pretty_assertions::assert_eq;
319 use std::collections::BTreeMap;
320
321 #[test]
322 fn from_empty() {
323 let input = "{}";
325 let expected_output = TracingConfig::default();
326
327 let actual_output = serde_yml::from_str::<TracingConfig>(input).unwrap();
329
330 assert_eq!(expected_output, actual_output);
332 }
333
334 #[test]
335 fn from_map_sparse() {
336 let input = r#"
338verbosity: off
339"#;
340 let expected_output = TracingConfig {
341 verbosity: Verbosity::Off,
342 ..TracingConfig::default()
343 };
344
345 let actual_output = serde_yml::from_str::<TracingConfig>(input).unwrap();
347
348 assert_eq!(expected_output, actual_output);
350 }
351
352 #[test]
353 fn from_map_full() {
354 let input = r#"
356verbosity: warn
357flavor: pretty
358show_color: false
359show_timestamp: false
360show_target: false
361show_file: true
362show_line_number: true
363show_level: false
364show_thread_id: false
365show_thread_name: true
366flatten_json: true
367targets:
368 crate_a: off
369 crate_b::module: error
370"#;
371 let expected_output = TracingConfig {
372 verbosity: Verbosity::Warn,
373 flavor: FormatFlavor::Pretty,
374 color: false,
375 show_timestamp: false,
376 show_target: false,
377 show_file: true,
378 show_line_number: true,
379 show_level: false,
380 show_thread_id: false,
381 show_thread_name: true,
382 #[cfg(feature = "json")]
383 flatten_json: true,
384 targets: BTreeMap::from([
385 ("crate_a".to_string(), Verbosity::Off),
386 ("crate_b::module".to_string(), Verbosity::Error),
387 ]),
388 };
389
390 let actual_output = serde_yml::from_str::<TracingConfig>(input).unwrap();
392
393 assert_eq!(expected_output, actual_output);
395 }
396}