1 | use std::borrow::Cow; |
2 | use std::cell::RefCell; |
3 | use std::fmt; |
4 | use std::io::{self, Write}; |
5 | use std::rc::Rc; |
6 | use std::sync::Mutex; |
7 | |
8 | use log::Level; |
9 | use termcolor::{self, ColorChoice, ColorSpec, WriteColor}; |
10 | |
11 | use crate::fmt::{Formatter, WritableTarget, WriteStyle}; |
12 | |
13 | pub(in crate::fmt::writer) mod glob { |
14 | pub use super::*; |
15 | } |
16 | |
17 | impl Formatter { |
18 | /// Begin a new [`Style`]. |
19 | /// |
20 | /// # Examples |
21 | /// |
22 | /// Create a bold, red colored style and use it to print the log level: |
23 | /// |
24 | /// ``` |
25 | /// use std::io::Write; |
26 | /// use env_logger::fmt::Color; |
27 | /// |
28 | /// let mut builder = env_logger::Builder::new(); |
29 | /// |
30 | /// builder.format(|buf, record| { |
31 | /// let mut level_style = buf.style(); |
32 | /// |
33 | /// level_style.set_color(Color::Red).set_bold(true); |
34 | /// |
35 | /// writeln!(buf, "{}: {}" , |
36 | /// level_style.value(record.level()), |
37 | /// record.args()) |
38 | /// }); |
39 | /// ``` |
40 | /// |
41 | /// [`Style`]: struct.Style.html |
42 | pub fn style(&self) -> Style { |
43 | Style { |
44 | buf: self.buf.clone(), |
45 | spec: ColorSpec::new(), |
46 | } |
47 | } |
48 | |
49 | /// Get the default [`Style`] for the given level. |
50 | /// |
51 | /// The style can be used to print other values besides the level. |
52 | pub fn default_level_style(&self, level: Level) -> Style { |
53 | let mut level_style = self.style(); |
54 | match level { |
55 | Level::Trace => level_style.set_color(Color::Cyan), |
56 | Level::Debug => level_style.set_color(Color::Blue), |
57 | Level::Info => level_style.set_color(Color::Green), |
58 | Level::Warn => level_style.set_color(Color::Yellow), |
59 | Level::Error => level_style.set_color(Color::Red).set_bold(true), |
60 | }; |
61 | level_style |
62 | } |
63 | |
64 | /// Get a printable [`Style`] for the given level. |
65 | /// |
66 | /// The style can only be used to print the level. |
67 | pub fn default_styled_level(&self, level: Level) -> StyledValue<'static, Level> { |
68 | self.default_level_style(level).into_value(level) |
69 | } |
70 | } |
71 | |
72 | pub(in crate::fmt::writer) struct BufferWriter { |
73 | inner: termcolor::BufferWriter, |
74 | uncolored_target: Option<WritableTarget>, |
75 | } |
76 | |
77 | pub(in crate::fmt) struct Buffer { |
78 | inner: termcolor::Buffer, |
79 | has_uncolored_target: bool, |
80 | } |
81 | |
82 | impl BufferWriter { |
83 | pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self { |
84 | BufferWriter { |
85 | inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), |
86 | uncolored_target: if is_test { |
87 | Some(WritableTarget::Stderr) |
88 | } else { |
89 | None |
90 | }, |
91 | } |
92 | } |
93 | |
94 | pub(in crate::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self { |
95 | BufferWriter { |
96 | inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()), |
97 | uncolored_target: if is_test { |
98 | Some(WritableTarget::Stdout) |
99 | } else { |
100 | None |
101 | }, |
102 | } |
103 | } |
104 | |
105 | pub(in crate::fmt::writer) fn pipe( |
106 | write_style: WriteStyle, |
107 | pipe: Box<Mutex<dyn io::Write + Send + 'static>>, |
108 | ) -> Self { |
109 | BufferWriter { |
110 | // The inner Buffer is never printed from, but it is still needed to handle coloring and other formatting |
111 | inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), |
112 | uncolored_target: Some(WritableTarget::Pipe(pipe)), |
113 | } |
114 | } |
115 | |
116 | pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { |
117 | Buffer { |
118 | inner: self.inner.buffer(), |
119 | has_uncolored_target: self.uncolored_target.is_some(), |
120 | } |
121 | } |
122 | |
123 | pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { |
124 | if let Some(target) = &self.uncolored_target { |
125 | // This impl uses the `eprint` and `print` macros |
126 | // instead of `termcolor`'s buffer. |
127 | // This is so their output can be captured by `cargo test` |
128 | let log = String::from_utf8_lossy(buf.bytes()); |
129 | |
130 | match target { |
131 | WritableTarget::Stderr => eprint!(" {}" , log), |
132 | WritableTarget::Stdout => print!(" {}" , log), |
133 | WritableTarget::Pipe(pipe) => write!(pipe.lock().unwrap(), " {}" , log)?, |
134 | } |
135 | |
136 | Ok(()) |
137 | } else { |
138 | self.inner.print(&buf.inner) |
139 | } |
140 | } |
141 | } |
142 | |
143 | impl Buffer { |
144 | pub(in crate::fmt) fn clear(&mut self) { |
145 | self.inner.clear() |
146 | } |
147 | |
148 | pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
149 | self.inner.write(buf) |
150 | } |
151 | |
152 | pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { |
153 | self.inner.flush() |
154 | } |
155 | |
156 | pub(in crate::fmt) fn bytes(&self) -> &[u8] { |
157 | self.inner.as_slice() |
158 | } |
159 | |
160 | fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
161 | // Ignore styles for test captured logs because they can't be printed |
162 | if !self.has_uncolored_target { |
163 | self.inner.set_color(spec) |
164 | } else { |
165 | Ok(()) |
166 | } |
167 | } |
168 | |
169 | fn reset(&mut self) -> io::Result<()> { |
170 | // Ignore styles for test captured logs because they can't be printed |
171 | if !self.has_uncolored_target { |
172 | self.inner.reset() |
173 | } else { |
174 | Ok(()) |
175 | } |
176 | } |
177 | } |
178 | |
179 | impl WriteStyle { |
180 | fn into_color_choice(self) -> ColorChoice { |
181 | match self { |
182 | WriteStyle::Always => ColorChoice::Always, |
183 | WriteStyle::Auto => ColorChoice::Auto, |
184 | WriteStyle::Never => ColorChoice::Never, |
185 | } |
186 | } |
187 | } |
188 | |
189 | /// A set of styles to apply to the terminal output. |
190 | /// |
191 | /// Call [`Formatter::style`] to get a `Style` and use the builder methods to |
192 | /// set styling properties, like [color] and [weight]. |
193 | /// To print a value using the style, wrap it in a call to [`value`] when the log |
194 | /// record is formatted. |
195 | /// |
196 | /// # Examples |
197 | /// |
198 | /// Create a bold, red colored style and use it to print the log level: |
199 | /// |
200 | /// ``` |
201 | /// use std::io::Write; |
202 | /// use env_logger::fmt::Color; |
203 | /// |
204 | /// let mut builder = env_logger::Builder::new(); |
205 | /// |
206 | /// builder.format(|buf, record| { |
207 | /// let mut level_style = buf.style(); |
208 | /// |
209 | /// level_style.set_color(Color::Red).set_bold(true); |
210 | /// |
211 | /// writeln!(buf, "{}: {}" , |
212 | /// level_style.value(record.level()), |
213 | /// record.args()) |
214 | /// }); |
215 | /// ``` |
216 | /// |
217 | /// Styles can be re-used to output multiple values: |
218 | /// |
219 | /// ``` |
220 | /// use std::io::Write; |
221 | /// use env_logger::fmt::Color; |
222 | /// |
223 | /// let mut builder = env_logger::Builder::new(); |
224 | /// |
225 | /// builder.format(|buf, record| { |
226 | /// let mut bold = buf.style(); |
227 | /// |
228 | /// bold.set_bold(true); |
229 | /// |
230 | /// writeln!(buf, "{}: {} {}" , |
231 | /// bold.value(record.level()), |
232 | /// bold.value("some bold text" ), |
233 | /// record.args()) |
234 | /// }); |
235 | /// ``` |
236 | /// |
237 | /// [`Formatter::style`]: struct.Formatter.html#method.style |
238 | /// [color]: #method.set_color |
239 | /// [weight]: #method.set_bold |
240 | /// [`value`]: #method.value |
241 | #[derive (Clone)] |
242 | pub struct Style { |
243 | buf: Rc<RefCell<Buffer>>, |
244 | spec: ColorSpec, |
245 | } |
246 | |
247 | /// A value that can be printed using the given styles. |
248 | /// |
249 | /// It is the result of calling [`Style::value`]. |
250 | /// |
251 | /// [`Style::value`]: struct.Style.html#method.value |
252 | pub struct StyledValue<'a, T> { |
253 | style: Cow<'a, Style>, |
254 | value: T, |
255 | } |
256 | |
257 | impl Style { |
258 | /// Set the text color. |
259 | /// |
260 | /// # Examples |
261 | /// |
262 | /// Create a style with red text: |
263 | /// |
264 | /// ``` |
265 | /// use std::io::Write; |
266 | /// use env_logger::fmt::Color; |
267 | /// |
268 | /// let mut builder = env_logger::Builder::new(); |
269 | /// |
270 | /// builder.format(|buf, record| { |
271 | /// let mut style = buf.style(); |
272 | /// |
273 | /// style.set_color(Color::Red); |
274 | /// |
275 | /// writeln!(buf, "{}" , style.value(record.args())) |
276 | /// }); |
277 | /// ``` |
278 | pub fn set_color(&mut self, color: Color) -> &mut Style { |
279 | self.spec.set_fg(Some(color.into_termcolor())); |
280 | self |
281 | } |
282 | |
283 | /// Set the text weight. |
284 | /// |
285 | /// If `yes` is true then text will be written in bold. |
286 | /// If `yes` is false then text will be written in the default weight. |
287 | /// |
288 | /// # Examples |
289 | /// |
290 | /// Create a style with bold text: |
291 | /// |
292 | /// ``` |
293 | /// use std::io::Write; |
294 | /// |
295 | /// let mut builder = env_logger::Builder::new(); |
296 | /// |
297 | /// builder.format(|buf, record| { |
298 | /// let mut style = buf.style(); |
299 | /// |
300 | /// style.set_bold(true); |
301 | /// |
302 | /// writeln!(buf, "{}" , style.value(record.args())) |
303 | /// }); |
304 | /// ``` |
305 | pub fn set_bold(&mut self, yes: bool) -> &mut Style { |
306 | self.spec.set_bold(yes); |
307 | self |
308 | } |
309 | |
310 | /// Set the text intensity. |
311 | /// |
312 | /// If `yes` is true then text will be written in a brighter color. |
313 | /// If `yes` is false then text will be written in the default color. |
314 | /// |
315 | /// # Examples |
316 | /// |
317 | /// Create a style with intense text: |
318 | /// |
319 | /// ``` |
320 | /// use std::io::Write; |
321 | /// |
322 | /// let mut builder = env_logger::Builder::new(); |
323 | /// |
324 | /// builder.format(|buf, record| { |
325 | /// let mut style = buf.style(); |
326 | /// |
327 | /// style.set_intense(true); |
328 | /// |
329 | /// writeln!(buf, "{}" , style.value(record.args())) |
330 | /// }); |
331 | /// ``` |
332 | pub fn set_intense(&mut self, yes: bool) -> &mut Style { |
333 | self.spec.set_intense(yes); |
334 | self |
335 | } |
336 | |
337 | /// Set whether the text is dimmed. |
338 | /// |
339 | /// If `yes` is true then text will be written in a dimmer color. |
340 | /// If `yes` is false then text will be written in the default color. |
341 | /// |
342 | /// # Examples |
343 | /// |
344 | /// Create a style with dimmed text: |
345 | /// |
346 | /// ``` |
347 | /// use std::io::Write; |
348 | /// |
349 | /// let mut builder = env_logger::Builder::new(); |
350 | /// |
351 | /// builder.format(|buf, record| { |
352 | /// let mut style = buf.style(); |
353 | /// |
354 | /// style.set_dimmed(true); |
355 | /// |
356 | /// writeln!(buf, "{}" , style.value(record.args())) |
357 | /// }); |
358 | /// ``` |
359 | pub fn set_dimmed(&mut self, yes: bool) -> &mut Style { |
360 | self.spec.set_dimmed(yes); |
361 | self |
362 | } |
363 | |
364 | /// Set the background color. |
365 | /// |
366 | /// # Examples |
367 | /// |
368 | /// Create a style with a yellow background: |
369 | /// |
370 | /// ``` |
371 | /// use std::io::Write; |
372 | /// use env_logger::fmt::Color; |
373 | /// |
374 | /// let mut builder = env_logger::Builder::new(); |
375 | /// |
376 | /// builder.format(|buf, record| { |
377 | /// let mut style = buf.style(); |
378 | /// |
379 | /// style.set_bg(Color::Yellow); |
380 | /// |
381 | /// writeln!(buf, "{}" , style.value(record.args())) |
382 | /// }); |
383 | /// ``` |
384 | pub fn set_bg(&mut self, color: Color) -> &mut Style { |
385 | self.spec.set_bg(Some(color.into_termcolor())); |
386 | self |
387 | } |
388 | |
389 | /// Wrap a value in the style. |
390 | /// |
391 | /// The same `Style` can be used to print multiple different values. |
392 | /// |
393 | /// # Examples |
394 | /// |
395 | /// Create a bold, red colored style and use it to print the log level: |
396 | /// |
397 | /// ``` |
398 | /// use std::io::Write; |
399 | /// use env_logger::fmt::Color; |
400 | /// |
401 | /// let mut builder = env_logger::Builder::new(); |
402 | /// |
403 | /// builder.format(|buf, record| { |
404 | /// let mut style = buf.style(); |
405 | /// |
406 | /// style.set_color(Color::Red).set_bold(true); |
407 | /// |
408 | /// writeln!(buf, "{}: {}" , |
409 | /// style.value(record.level()), |
410 | /// record.args()) |
411 | /// }); |
412 | /// ``` |
413 | pub fn value<T>(&self, value: T) -> StyledValue<T> { |
414 | StyledValue { |
415 | style: Cow::Borrowed(self), |
416 | value, |
417 | } |
418 | } |
419 | |
420 | /// Wrap a value in the style by taking ownership of it. |
421 | pub(crate) fn into_value<T>(self, value: T) -> StyledValue<'static, T> { |
422 | StyledValue { |
423 | style: Cow::Owned(self), |
424 | value, |
425 | } |
426 | } |
427 | } |
428 | |
429 | impl<'a, T> StyledValue<'a, T> { |
430 | fn write_fmt<F>(&self, f: F) -> fmt::Result |
431 | where |
432 | F: FnOnce() -> fmt::Result, |
433 | { |
434 | self.style |
435 | .buf |
436 | .borrow_mut() |
437 | .set_color(&self.style.spec) |
438 | .map_err(|_| fmt::Error)?; |
439 | |
440 | // Always try to reset the terminal style, even if writing failed |
441 | let write: Result<(), Error> = f(); |
442 | let reset: Result<(), Error> = self.style.buf.borrow_mut().reset().map_err(|_| fmt::Error); |
443 | |
444 | write.and(res:reset) |
445 | } |
446 | } |
447 | |
448 | impl fmt::Debug for Style { |
449 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
450 | f.debug_struct("Style" ).field(name:"spec" , &self.spec).finish() |
451 | } |
452 | } |
453 | |
454 | macro_rules! impl_styled_value_fmt { |
455 | ($($fmt_trait:path),*) => { |
456 | $( |
457 | impl<'a, T: $fmt_trait> $fmt_trait for StyledValue<'a, T> { |
458 | fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { |
459 | self.write_fmt(|| T::fmt(&self.value, f)) |
460 | } |
461 | } |
462 | )* |
463 | }; |
464 | } |
465 | |
466 | impl_styled_value_fmt!( |
467 | fmt::Debug, |
468 | fmt::Display, |
469 | fmt::Pointer, |
470 | fmt::Octal, |
471 | fmt::Binary, |
472 | fmt::UpperHex, |
473 | fmt::LowerHex, |
474 | fmt::UpperExp, |
475 | fmt::LowerExp |
476 | ); |
477 | |
478 | // The `Color` type is copied from https://github.com/BurntSushi/termcolor |
479 | |
480 | /// The set of available colors for the terminal foreground/background. |
481 | /// |
482 | /// The `Ansi256` and `Rgb` colors will only output the correct codes when |
483 | /// paired with the `Ansi` `WriteColor` implementation. |
484 | /// |
485 | /// The `Ansi256` and `Rgb` color types are not supported when writing colors |
486 | /// on Windows using the console. If they are used on Windows, then they are |
487 | /// silently ignored and no colors will be emitted. |
488 | /// |
489 | /// This set may expand over time. |
490 | /// |
491 | /// This type has a `FromStr` impl that can parse colors from their human |
492 | /// readable form. The format is as follows: |
493 | /// |
494 | /// 1. Any of the explicitly listed colors in English. They are matched |
495 | /// case insensitively. |
496 | /// 2. A single 8-bit integer, in either decimal or hexadecimal format. |
497 | /// 3. A triple of 8-bit integers separated by a comma, where each integer is |
498 | /// in decimal or hexadecimal format. |
499 | /// |
500 | /// Hexadecimal numbers are written with a `0x` prefix. |
501 | #[allow (missing_docs)] |
502 | #[non_exhaustive ] |
503 | #[derive (Clone, Debug, Eq, PartialEq)] |
504 | pub enum Color { |
505 | Black, |
506 | Blue, |
507 | Green, |
508 | Red, |
509 | Cyan, |
510 | Magenta, |
511 | Yellow, |
512 | White, |
513 | Ansi256(u8), |
514 | Rgb(u8, u8, u8), |
515 | } |
516 | |
517 | impl Color { |
518 | fn into_termcolor(self) -> termcolor::Color { |
519 | match self { |
520 | Color::Black => termcolor::Color::Black, |
521 | Color::Blue => termcolor::Color::Blue, |
522 | Color::Green => termcolor::Color::Green, |
523 | Color::Red => termcolor::Color::Red, |
524 | Color::Cyan => termcolor::Color::Cyan, |
525 | Color::Magenta => termcolor::Color::Magenta, |
526 | Color::Yellow => termcolor::Color::Yellow, |
527 | Color::White => termcolor::Color::White, |
528 | Color::Ansi256(value: u8) => termcolor::Color::Ansi256(value), |
529 | Color::Rgb(r: u8, g: u8, b: u8) => termcolor::Color::Rgb(r, g, b), |
530 | } |
531 | } |
532 | } |
533 | |