tari_log4rs/append/
console.rs

1//! The console appender.
2//!
3//! Requires the `console_appender` feature.
4
5use derivative::Derivative;
6use log::Record;
7use std::{
8    fmt,
9    io::{self, Write},
10};
11
12#[cfg(feature = "config_parsing")]
13use crate::config::{Deserialize, Deserializers};
14#[cfg(feature = "config_parsing")]
15use crate::encode::EncoderConfig;
16use crate::{
17    append::Append,
18    encode::{
19        self,
20        pattern::PatternEncoder,
21        writer::{
22            console::{ConsoleWriter, ConsoleWriterLock},
23            simple::SimpleWriter,
24        },
25        Encode, Style,
26    },
27    priv_io::{StdWriter, StdWriterLock},
28};
29
30/// The console appender's configuration.
31#[cfg(feature = "config_parsing")]
32#[derive(Debug, serde::Deserialize)]
33#[serde(deny_unknown_fields)]
34pub struct ConsoleAppenderConfig {
35    target: Option<ConfigTarget>,
36    encoder: Option<EncoderConfig>,
37    tty_only: Option<bool>,
38}
39
40#[cfg(feature = "config_parsing")]
41#[derive(Debug, serde::Deserialize)]
42enum ConfigTarget {
43    #[serde(rename = "stdout")]
44    Stdout,
45    #[serde(rename = "stderr")]
46    Stderr,
47}
48
49enum Writer {
50    Tty(ConsoleWriter),
51    Raw(StdWriter),
52}
53
54impl Writer {
55    fn lock(&self) -> WriterLock {
56        match *self {
57            Writer::Tty(ref w) => WriterLock::Tty(w.lock()),
58            Writer::Raw(ref w) => WriterLock::Raw(SimpleWriter(w.lock())),
59        }
60    }
61
62    fn is_tty(&self) -> bool {
63        // 1.40 compat
64        #[allow(clippy::match_like_matches_macro)]
65        match self {
66            Self::Tty(_) => true,
67            _ => false,
68        }
69    }
70}
71
72enum WriterLock<'a> {
73    Tty(ConsoleWriterLock<'a>),
74    Raw(SimpleWriter<StdWriterLock<'a>>),
75}
76
77impl<'a> io::Write for WriterLock<'a> {
78    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
79        match *self {
80            WriterLock::Tty(ref mut w) => w.write(buf),
81            WriterLock::Raw(ref mut w) => w.write(buf),
82        }
83    }
84
85    fn flush(&mut self) -> io::Result<()> {
86        match *self {
87            WriterLock::Tty(ref mut w) => w.flush(),
88            WriterLock::Raw(ref mut w) => w.flush(),
89        }
90    }
91
92    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
93        match *self {
94            WriterLock::Tty(ref mut w) => w.write_all(buf),
95            WriterLock::Raw(ref mut w) => w.write_all(buf),
96        }
97    }
98
99    fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
100        match *self {
101            WriterLock::Tty(ref mut w) => w.write_fmt(fmt),
102            WriterLock::Raw(ref mut w) => w.write_fmt(fmt),
103        }
104    }
105}
106
107impl<'a> encode::Write for WriterLock<'a> {
108    fn set_style(&mut self, style: &Style) -> io::Result<()> {
109        match *self {
110            WriterLock::Tty(ref mut w) => w.set_style(style),
111            WriterLock::Raw(ref mut w) => w.set_style(style),
112        }
113    }
114}
115
116/// An appender which logs to standard out.
117///
118/// It supports output styling if standard out is a console buffer on Windows
119/// or is a TTY on Unix.
120#[derive(Derivative)]
121#[derivative(Debug)]
122pub struct ConsoleAppender {
123    #[derivative(Debug = "ignore")]
124    writer: Writer,
125    encoder: Box<dyn Encode>,
126    do_write: bool,
127}
128
129impl Append for ConsoleAppender {
130    fn append(&self, record: &Record) -> anyhow::Result<()> {
131        if self.do_write {
132            let mut writer = self.writer.lock();
133            self.encoder.encode(&mut writer, record)?;
134            writer.flush()?;
135        }
136        Ok(())
137    }
138
139    fn flush(&self) {}
140}
141
142impl ConsoleAppender {
143    /// Creates a new `ConsoleAppender` builder.
144    pub fn builder() -> ConsoleAppenderBuilder {
145        ConsoleAppenderBuilder {
146            encoder: None,
147            target: Target::Stdout,
148            tty_only: false,
149        }
150    }
151}
152
153/// A builder for `ConsoleAppender`s.
154pub struct ConsoleAppenderBuilder {
155    encoder: Option<Box<dyn Encode>>,
156    target: Target,
157    tty_only: bool,
158}
159
160impl ConsoleAppenderBuilder {
161    /// Sets the output encoder for the `ConsoleAppender`.
162    pub fn encoder(mut self, encoder: Box<dyn Encode>) -> ConsoleAppenderBuilder {
163        self.encoder = Some(encoder);
164        self
165    }
166
167    /// Sets the output stream to log to.
168    ///
169    /// Defaults to `Target::Stdout`.
170    pub fn target(mut self, target: Target) -> ConsoleAppenderBuilder {
171        self.target = target;
172        self
173    }
174
175    /// Sets the output to log only when it's a TTY.
176    ///
177    /// Defaults to `false`.
178    pub fn tty_only(mut self, tty_only: bool) -> ConsoleAppenderBuilder {
179        self.tty_only = tty_only;
180        self
181    }
182
183    /// Consumes the `ConsoleAppenderBuilder`, producing a `ConsoleAppender`.
184    pub fn build(self) -> ConsoleAppender {
185        let writer = match self.target {
186            Target::Stderr => match ConsoleWriter::stderr() {
187                Some(writer) => Writer::Tty(writer),
188                None => Writer::Raw(StdWriter::stderr()),
189            },
190            Target::Stdout => match ConsoleWriter::stdout() {
191                Some(writer) => Writer::Tty(writer),
192                None => Writer::Raw(StdWriter::stdout()),
193            },
194        };
195
196        let do_write = writer.is_tty() || !self.tty_only;
197
198        ConsoleAppender {
199            writer,
200            encoder: self
201                .encoder
202                .unwrap_or_else(|| Box::new(PatternEncoder::default())),
203            do_write,
204        }
205    }
206}
207
208/// The stream to log to.
209#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
210pub enum Target {
211    /// Standard output.
212    Stdout,
213    /// Standard error.
214    Stderr,
215}
216
217/// A deserializer for the `ConsoleAppender`.
218///
219/// # Configuration
220///
221/// ```yaml
222/// kind: console
223///
224/// # The output to write to. One of `stdout` or `stderr`. Defaults to `stdout`.
225/// target: stdout
226///
227/// # Set this boolean when the console appender must only write when the target is a TTY.
228/// tty_only: false
229///
230/// # The encoder to use to format output. Defaults to `kind: pattern`.
231/// encoder:
232///   kind: pattern
233/// ```
234#[cfg(feature = "config_parsing")]
235#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
236pub struct ConsoleAppenderDeserializer;
237
238#[cfg(feature = "config_parsing")]
239impl Deserialize for ConsoleAppenderDeserializer {
240    type Trait = dyn Append;
241
242    type Config = ConsoleAppenderConfig;
243
244    fn deserialize(
245        &self,
246        config: ConsoleAppenderConfig,
247        deserializers: &Deserializers,
248    ) -> anyhow::Result<Box<dyn Append>> {
249        let mut appender = ConsoleAppender::builder();
250        if let Some(target) = config.target {
251            let target = match target {
252                ConfigTarget::Stdout => Target::Stdout,
253                ConfigTarget::Stderr => Target::Stderr,
254            };
255            appender = appender.target(target);
256        }
257        if let Some(tty_only) = config.tty_only {
258            appender = appender.tty_only(tty_only);
259        }
260        if let Some(encoder) = config.encoder {
261            appender = appender.encoder(deserializers.deserialize(&encoder.kind, encoder.config)?);
262        }
263        Ok(Box::new(appender.build()))
264    }
265}