env_logger/fmt/writer/
mod.rs

1mod atty;
2mod buffer;
3
4use self::atty::{is_stderr, is_stdout};
5use self::buffer::BufferWriter;
6use std::{fmt, io, mem, sync::Mutex};
7
8pub(super) mod glob {
9    pub use super::*;
10}
11
12pub(super) use self::buffer::Buffer;
13
14/// Log target, either `stdout`, `stderr` or a custom pipe.
15#[non_exhaustive]
16pub enum Target {
17    /// Logs will be sent to standard output.
18    Stdout,
19    /// Logs will be sent to standard error.
20    Stderr,
21    /// Logs will be sent to a custom pipe.
22    Pipe(Box<dyn io::Write + Send + 'static>),
23}
24
25impl Default for Target {
26    fn default() -> Self {
27        Target::Stderr
28    }
29}
30
31impl fmt::Debug for Target {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        write!(
34            f,
35            "{}",
36            match self {
37                Self::Stdout => "stdout",
38                Self::Stderr => "stderr",
39                Self::Pipe(_) => "pipe",
40            }
41        )
42    }
43}
44
45/// Log target, either `stdout`, `stderr` or a custom pipe.
46///
47/// Same as `Target`, except the pipe is wrapped in a mutex for interior mutability.
48pub(super) enum WritableTarget {
49    /// Logs will be written to standard output.
50    #[allow(dead_code)]
51    WriteStdout,
52    /// Logs will be printed to standard output.
53    PrintStdout,
54    /// Logs will be written to standard error.
55    #[allow(dead_code)]
56    WriteStderr,
57    /// Logs will be printed to standard error.
58    PrintStderr,
59    /// Logs will be sent to a custom pipe.
60    Pipe(Box<Mutex<dyn io::Write + Send + 'static>>),
61}
62
63impl WritableTarget {
64    fn print(&self, buf: &Buffer) -> io::Result<()> {
65        use std::io::Write as _;
66
67        let buf = buf.as_bytes();
68        match self {
69            WritableTarget::WriteStdout => {
70                let stream = std::io::stdout();
71                let mut stream = stream.lock();
72                stream.write_all(buf)?;
73                stream.flush()?;
74            }
75            WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)),
76            WritableTarget::WriteStderr => {
77                let stream = std::io::stderr();
78                let mut stream = stream.lock();
79                stream.write_all(buf)?;
80                stream.flush()?;
81            }
82            WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(buf)),
83            // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty.
84            WritableTarget::Pipe(pipe) => {
85                let mut stream = pipe.lock().unwrap();
86                stream.write_all(buf)?;
87                stream.flush()?;
88            }
89        }
90
91        Ok(())
92    }
93}
94
95impl fmt::Debug for WritableTarget {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(
98            f,
99            "{}",
100            match self {
101                Self::WriteStdout => "stdout",
102                Self::PrintStdout => "stdout",
103                Self::WriteStderr => "stderr",
104                Self::PrintStderr => "stderr",
105                Self::Pipe(_) => "pipe",
106            }
107        )
108    }
109}
110/// Whether or not to print styles to the target.
111#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
112pub enum WriteStyle {
113    /// Try to print styles, but don't force the issue.
114    Auto,
115    /// Try very hard to print styles.
116    Always,
117    /// Never print styles.
118    Never,
119}
120
121impl Default for WriteStyle {
122    fn default() -> Self {
123        WriteStyle::Auto
124    }
125}
126
127#[cfg(feature = "color")]
128impl WriteStyle {
129    fn into_color_choice(self) -> ::termcolor::ColorChoice {
130        match self {
131            WriteStyle::Always => ::termcolor::ColorChoice::Always,
132            WriteStyle::Auto => ::termcolor::ColorChoice::Auto,
133            WriteStyle::Never => ::termcolor::ColorChoice::Never,
134        }
135    }
136}
137
138/// A terminal target with color awareness.
139pub(crate) struct Writer {
140    inner: BufferWriter,
141}
142
143impl Writer {
144    pub fn write_style(&self) -> WriteStyle {
145        self.inner.write_style()
146    }
147
148    pub(super) fn buffer(&self) -> Buffer {
149        self.inner.buffer()
150    }
151
152    pub(super) fn print(&self, buf: &Buffer) -> io::Result<()> {
153        self.inner.print(buf)
154    }
155}
156
157impl fmt::Debug for Writer {
158    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159        f.debug_struct("Writer").finish()
160    }
161}
162
163/// A builder for a terminal writer.
164///
165/// The target and style choice can be configured before building.
166#[derive(Debug)]
167pub(crate) struct Builder {
168    target: Target,
169    write_style: WriteStyle,
170    is_test: bool,
171    built: bool,
172}
173
174impl Builder {
175    /// Initialize the writer builder with defaults.
176    pub(crate) fn new() -> Self {
177        Builder {
178            target: Default::default(),
179            write_style: Default::default(),
180            is_test: false,
181            built: false,
182        }
183    }
184
185    /// Set the target to write to.
186    pub(crate) fn target(&mut self, target: Target) -> &mut Self {
187        self.target = target;
188        self
189    }
190
191    /// Parses a style choice string.
192    ///
193    /// See the [Disabling colors] section for more details.
194    ///
195    /// [Disabling colors]: ../index.html#disabling-colors
196    pub(crate) fn parse_write_style(&mut self, write_style: &str) -> &mut Self {
197        self.write_style(parse_write_style(write_style))
198    }
199
200    /// Whether or not to print style characters when writing.
201    pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self {
202        self.write_style = write_style;
203        self
204    }
205
206    /// Whether or not to capture logs for `cargo test`.
207    #[allow(clippy::wrong_self_convention)]
208    pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self {
209        self.is_test = is_test;
210        self
211    }
212
213    /// Build a terminal writer.
214    pub(crate) fn build(&mut self) -> Writer {
215        assert!(!self.built, "attempt to re-use consumed builder");
216        self.built = true;
217
218        let color_choice = match self.write_style {
219            WriteStyle::Auto => {
220                if match &self.target {
221                    Target::Stderr => is_stderr(),
222                    Target::Stdout => is_stdout(),
223                    Target::Pipe(_) => false,
224                } {
225                    WriteStyle::Auto
226                } else {
227                    WriteStyle::Never
228                }
229            }
230            color_choice => color_choice,
231        };
232        let color_choice = if self.is_test {
233            WriteStyle::Never
234        } else {
235            color_choice
236        };
237
238        let writer = match mem::take(&mut self.target) {
239            Target::Stderr => BufferWriter::stderr(self.is_test, color_choice),
240            Target::Stdout => BufferWriter::stdout(self.is_test, color_choice),
241            Target::Pipe(pipe) => BufferWriter::pipe(Box::new(Mutex::new(pipe))),
242        };
243
244        Writer { inner: writer }
245    }
246}
247
248impl Default for Builder {
249    fn default() -> Self {
250        Builder::new()
251    }
252}
253
254fn parse_write_style(spec: &str) -> WriteStyle {
255    match spec {
256        "auto" => WriteStyle::Auto,
257        "always" => WriteStyle::Always,
258        "never" => WriteStyle::Never,
259        _ => Default::default(),
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[test]
268    fn parse_write_style_valid() {
269        let inputs = vec![
270            ("auto", WriteStyle::Auto),
271            ("always", WriteStyle::Always),
272            ("never", WriteStyle::Never),
273        ];
274
275        for (input, expected) in inputs {
276            assert_eq!(expected, parse_write_style(input));
277        }
278    }
279
280    #[test]
281    fn parse_write_style_invalid() {
282        let inputs = vec!["", "true", "false", "NEVER!!"];
283
284        for input in inputs {
285            assert_eq!(WriteStyle::Auto, parse_write_style(input));
286        }
287    }
288}