1use 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#[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 #[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#[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 pub fn builder() -> ConsoleAppenderBuilder {
145 ConsoleAppenderBuilder {
146 encoder: None,
147 target: Target::Stdout,
148 tty_only: false,
149 }
150 }
151}
152
153pub struct ConsoleAppenderBuilder {
155 encoder: Option<Box<dyn Encode>>,
156 target: Target,
157 tty_only: bool,
158}
159
160impl ConsoleAppenderBuilder {
161 pub fn encoder(mut self, encoder: Box<dyn Encode>) -> ConsoleAppenderBuilder {
163 self.encoder = Some(encoder);
164 self
165 }
166
167 pub fn target(mut self, target: Target) -> ConsoleAppenderBuilder {
171 self.target = target;
172 self
173 }
174
175 pub fn tty_only(mut self, tty_only: bool) -> ConsoleAppenderBuilder {
179 self.tty_only = tty_only;
180 self
181 }
182
183 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#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
210pub enum Target {
211 Stdout,
213 Stderr,
215}
216
217#[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}