simplelog/loggers/
termlog.rs

1//! Module providing the TermLogger Implementation
2
3use log::{
4    set_boxed_logger, set_max_level, Level, LevelFilter, Log, Metadata, Record, SetLoggerError,
5};
6use std::io::{Error, Write};
7use std::sync::Mutex;
8use termcolor::{BufferedStandardStream, ColorChoice};
9#[cfg(not(feature = "ansi_term"))]
10use termcolor::{ColorSpec, WriteColor};
11
12use super::logging::*;
13
14use crate::{Config, SharedLogger, ThreadLogMode};
15
16struct OutputStreams {
17    err: BufferedStandardStream,
18    out: BufferedStandardStream,
19}
20
21/// Specifies which streams should be used when logging
22#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
23pub enum TerminalMode {
24    /// Only use Stdout
25    Stdout,
26    /// Only use Stderr
27    Stderr,
28    /// Use Stderr for Errors and Stdout otherwise
29    Mixed,
30}
31
32impl Default for TerminalMode {
33    fn default() -> TerminalMode {
34        TerminalMode::Mixed
35    }
36}
37
38/// The TermLogger struct. Provides a stderr/out based Logger implementation
39///
40/// Supports colored output
41pub struct TermLogger {
42    level: LevelFilter,
43    config: Config,
44    streams: Mutex<OutputStreams>,
45}
46
47impl TermLogger {
48    /// init function. Globally initializes the TermLogger as the one and only used log facility.
49    ///
50    /// Takes the desired `Level` and `Config` as arguments. They cannot be changed later on.
51    /// Fails if another Logger was already initialized
52    ///
53    /// # Examples
54    /// ```
55    /// # extern crate simplelog;
56    /// # use simplelog::*;
57    /// # fn main() {
58    ///     TermLogger::init(
59    ///         LevelFilter::Info,
60    ///         Config::default(),
61    ///         TerminalMode::Mixed,
62    ///         ColorChoice::Auto
63    ///     );
64    /// # }
65    /// ```
66    pub fn init(
67        log_level: LevelFilter,
68        config: Config,
69        mode: TerminalMode,
70        color_choice: ColorChoice,
71    ) -> Result<(), SetLoggerError> {
72        let logger = TermLogger::new(log_level, config, mode, color_choice);
73        set_max_level(log_level);
74        set_boxed_logger(logger)?;
75        Ok(())
76    }
77
78    /// allows to create a new logger, that can be independently used, no matter whats globally set.
79    ///
80    /// no macros are provided for this case and you probably
81    /// dont want to use this function, but `init()`, if you dont want to build a `CombinedLogger`.
82    ///
83    /// Takes the desired `Level` and `Config` as arguments. They cannot be changed later on.
84    ///
85    /// Returns a `Box`ed TermLogger
86    ///
87    /// # Examples
88    /// ```
89    /// # extern crate simplelog;
90    /// # use simplelog::*;
91    /// # fn main() {
92    /// let term_logger = TermLogger::new(
93    ///     LevelFilter::Info,
94    ///     Config::default(),
95    ///     TerminalMode::Mixed,
96    ///     ColorChoice::Auto
97    /// );
98    /// # }
99    /// ```
100    pub fn new(
101        log_level: LevelFilter,
102        config: Config,
103        mode: TerminalMode,
104        color_choice: ColorChoice,
105    ) -> Box<TermLogger> {
106        let streams = match mode {
107            TerminalMode::Stdout => OutputStreams {
108                err: BufferedStandardStream::stdout(color_choice),
109                out: BufferedStandardStream::stdout(color_choice),
110            },
111            TerminalMode::Stderr => OutputStreams {
112                err: BufferedStandardStream::stderr(color_choice),
113                out: BufferedStandardStream::stderr(color_choice),
114            },
115            TerminalMode::Mixed => OutputStreams {
116                err: BufferedStandardStream::stderr(color_choice),
117                out: BufferedStandardStream::stdout(color_choice),
118            },
119        };
120
121        Box::new(TermLogger {
122            level: log_level,
123            config,
124            streams: Mutex::new(streams),
125        })
126    }
127
128    fn try_log_term(
129        &self,
130        record: &Record<'_>,
131        term_lock: &mut BufferedStandardStream,
132    ) -> Result<(), Error> {
133        #[cfg(not(feature = "ansi_term"))]
134        let color = self.config.level_color[record.level() as usize];
135
136        if self.config.time <= record.level() && self.config.time != LevelFilter::Off {
137            write_time(term_lock, &self.config)?;
138        }
139
140        if self.config.level <= record.level() && self.config.level != LevelFilter::Off {
141            #[cfg(not(feature = "ansi_term"))]
142            if !self.config.write_log_enable_colors {
143                term_lock.set_color(ColorSpec::new().set_fg(color))?;
144            }
145
146            write_level(record, term_lock, &self.config)?;
147
148            #[cfg(not(feature = "ansi_term"))]
149            if !self.config.write_log_enable_colors {
150                term_lock.reset()?;
151            }
152        }
153
154        if self.config.thread <= record.level() && self.config.thread != LevelFilter::Off {
155            match self.config.thread_log_mode {
156                ThreadLogMode::IDs => {
157                    write_thread_id(term_lock, &self.config)?;
158                }
159                ThreadLogMode::Names | ThreadLogMode::Both => {
160                    write_thread_name(term_lock, &self.config)?;
161                }
162            }
163        }
164
165        if self.config.target <= record.level() && self.config.target != LevelFilter::Off {
166            write_target(record, term_lock, &self.config)?;
167        }
168
169        if self.config.location <= record.level() && self.config.location != LevelFilter::Off {
170            write_location(record, term_lock)?;
171        }
172
173        write_args(record, term_lock)?;
174
175        // The log crate holds the logger as a `static mut`, which isn't dropped
176        // at program exit: https://doc.rust-lang.org/reference/items/static-items.html
177        // Sadly, this means we can't rely on the BufferedStandardStreams flushing
178        // themselves on the way out, so to avoid the Case of the Missing 8k,
179        // flush each entry.
180        term_lock.flush()
181    }
182
183    fn try_log(&self, record: &Record<'_>) -> Result<(), Error> {
184        if self.enabled(record.metadata()) {
185            if should_skip(&self.config, record) {
186                return Ok(());
187            }
188
189            let mut streams = self.streams.lock().unwrap();
190
191            if record.level() == Level::Error {
192                self.try_log_term(record, &mut streams.err)
193            } else {
194                self.try_log_term(record, &mut streams.out)
195            }
196        } else {
197            Ok(())
198        }
199    }
200}
201
202impl Log for TermLogger {
203    fn enabled(&self, metadata: &Metadata<'_>) -> bool {
204        metadata.level() <= self.level
205    }
206
207    fn log(&self, record: &Record<'_>) {
208        let _ = self.try_log(record);
209    }
210
211    fn flush(&self) {
212        let mut streams = self.streams.lock().unwrap();
213        let _ = streams.out.flush();
214        let _ = streams.err.flush();
215    }
216}
217
218impl SharedLogger for TermLogger {
219    fn level(&self) -> LevelFilter {
220        self.level
221    }
222
223    fn config(&self) -> Option<&Config> {
224        Some(&self.config)
225    }
226
227    fn as_log(self: Box<Self>) -> Box<dyn Log> {
228        Box::new(*self)
229    }
230}