fern/meta.rs
1/*!
2Fern supports logging most things by default, except for one kind of struct: structs which make log
3calls to the global logger from within their `Display` or `Debug` implementations.
4
5Here's an example of such a structure:
6
7```
8# use log::debug;
9# use std::fmt;
10#
11struct Thing<'a>(&'a str);
12
13impl<'a> fmt::Display for Thing<'a> {
14 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15 debug!("just displayed a Thing wrapping {}", self.0);
16 f.write_str(self.0)
17 }
18}
19
20# fn main() {}
21```
22
23This structure, and this structure alone, will cause some problems when logging in fern. There are
24mitigations, but since it's a fairly niche use case, they are disabled by default.
25
26The problems are, depending on which backend you use:
27
28- stdout/stderr: logging will 'stutter', with the logs output inside the `Display` implementation
29 cutting other log lines down the center
30- file: thread will deadlock, and all future logs will also deadlock
31
32There are two mitigations you can make, both completely fix this error.
33
34The simplest mitigation to this is to enable the `meta-logging-in-format` feature of `fern`. The
35disadvantage is that this means fern makes an additional allocation per log call per affected
36backend. Not a huge cost, but enough to mean it's disabled by default. To enable this, use the
37following in your `Cargo.toml`:
38
39```toml
40[dependencies]
41# ...
42fern = { version = "0.7", features = ["meta-logging-in-format"] }
43```
44
45The second mitigation is one you can make inside a formatting closure. This means extra code
46complexity, but it also means you can enable it per-logger: the fix isn't global. This fix is also
47redundant if you've already enable the above feature. To add the second mitigation, replacec
48`format_args!()` with `format!()` as displayed below:
49
50```
51fern::Dispatch::new()
52 # /*
53 ...
54 # */
55 // instead of doing this:
56 .format(move |out, message, record| {
57 out.finish(format_args!("[{}] {}", record.level(), message))
58 })
59 // do this:
60 .format(move |out, message, record| {
61 let formatted = format!("[{}] {}", record.level(), message);
62
63 out.finish(format_args!("{}", formatted))
64 })
65# ;
66```
67
68This second mitigation works by forcing the `Display` implementation to run before any text has
69started to log to the backend. There's an additional allocation per log, but it no longer deadlocks!
70
71This mitigation also has the advantage of ensuring there's only one call to `Display::fmt`. If youc
72use `meta-logging-in-format` and have multiple backends, `Display::fmt` will still be called once
73per backend. With this, it will only be called once.
74
75------
76
77If you've never experienced this problem, there's no need to fix it - `Display::fmt` and
78`Debug::fmt` are normally implemented as "pure" functions with no side effects.
79*/