Skip to main content

spdlog/sink/
std_stream_sink.rs

1//! Provides a std stream sink.
2
3use std::{
4    convert::Infallible,
5    io::{self, IsTerminal as _, Write},
6    // Import `str` module for function `std::str::from_utf8`, because method `str::from_utf8` is
7    // stabilized since Rust 1.87.
8    //
9    // TODO: Remove this import when our MSRV reaches Rust 1.87.
10    str,
11};
12
13use crate::{
14    formatter::{Formatter, FormatterContext},
15    sink::{GetSinkProp, Sink, SinkProp},
16    sync::*,
17    terminal_style::{LevelStyles, Style, StyleMode},
18    Error, ErrorHandler, Level, LevelFilter, Record, Result, StringBuf,
19};
20
21/// An enum representing the available standard streams.
22#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
23pub enum StdStream {
24    /// Standard output.
25    Stdout,
26    /// Standard error.
27    Stderr,
28}
29
30impl StdStream {
31    fn via_write(&self) -> StdStreamDest<io::StdoutLock<'_>, io::StderrLock<'_>> {
32        match self {
33            Self::Stdout => StdStreamDest::Stdout(io::stdout().lock()),
34            Self::Stderr => StdStreamDest::Stderr(io::stderr().lock()),
35        }
36    }
37
38    fn via_macro(&self) -> StdStreamDest<via_macro::Stdout, via_macro::Stderr> {
39        match self {
40            Self::Stdout => StdStreamDest::Stdout(via_macro::Stdout),
41            Self::Stderr => StdStreamDest::Stderr(via_macro::Stderr),
42        }
43    }
44}
45
46// `io::stdout()` and `io::stderr()` return different types, and
47// `Std***::lock()` is not in any trait, so we need this struct to abstract
48// them.
49#[derive(Debug)]
50enum StdStreamDest<O, E> {
51    Stdout(O),
52    Stderr(E),
53}
54
55impl<O, E> StdStreamDest<O, E> {
56    #[allow(dead_code)]
57    fn stream_type(&self) -> StdStream {
58        match self {
59            Self::Stdout(_) => StdStream::Stdout,
60            Self::Stderr(_) => StdStream::Stderr,
61        }
62    }
63}
64
65macro_rules! impl_write_for_dest {
66    ( $dest:ty ) => {
67        impl Write for $dest {
68            fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
69                match self {
70                    StdStreamDest::Stdout(stream) => stream.write(buf),
71                    StdStreamDest::Stderr(stream) => stream.write(buf),
72                }
73            }
74
75            fn flush(&mut self) -> io::Result<()> {
76                match self {
77                    StdStreamDest::Stdout(stream) => stream.flush(),
78                    StdStreamDest::Stderr(stream) => stream.flush(),
79                }
80            }
81        }
82    };
83}
84impl_write_for_dest!(StdStreamDest<io::StdoutLock<'_>, io::StderrLock<'_>>);
85impl_write_for_dest!(StdStreamDest<via_macro::Stdout, via_macro::Stderr>);
86
87mod via_macro {
88    use super::*;
89
90    fn bytes_to_str(buf: &[u8]) -> io::Result<&str> {
91        str::from_utf8(buf).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
92    }
93
94    pub(crate) struct Stdout;
95
96    impl Write for Stdout {
97        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
98            print!("{}", bytes_to_str(buf)?);
99            Ok(buf.len())
100        }
101
102        fn flush(&mut self) -> io::Result<()> {
103            io::stdout().flush()
104        }
105    }
106
107    pub(crate) struct Stderr;
108
109    impl Write for Stderr {
110        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
111            eprint!("{}", bytes_to_str(buf)?);
112            Ok(buf.len())
113        }
114
115        fn flush(&mut self) -> io::Result<()> {
116            io::stderr().flush()
117        }
118    }
119}
120
121/// A sink with a std stream as the target.
122///
123/// It writes styled text or plain text according to the given [`StyleMode`] and
124/// the current terminal environment.
125///
126/// Note that this sink always flushes the buffer once with each logging.
127pub struct StdStreamSink {
128    prop: SinkProp,
129    via_print_macro: bool,
130    std_stream: StdStream,
131    should_render_style: bool,
132    level_styles: LevelStyles,
133}
134
135impl StdStreamSink {
136    /// Gets a builder of `StdStreamSink` with default parameters:
137    ///
138    /// | Parameter         | Default Value                                                       |
139    /// |-------------------|---------------------------------------------------------------------|
140    /// | [level_filter]    | [`LevelFilter::All`]                                                |
141    /// | [formatter]       | [`FullFormatter`]                                                   |
142    /// | [error_handler]   | [`ErrorHandler::default()`]                                         |
143    /// |                   |                                                                     |
144    /// | [std_stream]      | *must be specified*                                                 |
145    /// | [style_mode]      | [`StyleMode::Auto`]                                                 |
146    /// | [via_print_macro] | `false`, or `true` if feature gate `std-stream-captured` is enabled |
147    ///
148    /// [level_filter]: StdStreamSinkBuilder::level_filter
149    /// [formatter]: StdStreamSinkBuilder::formatter
150    /// [`FullFormatter`]: crate::formatter::FullFormatter
151    /// [error_handler]: StdStreamSinkBuilder::error_handler
152    /// [std_stream]: StdStreamSinkBuilder::std_stream
153    /// [style_mode]: StdStreamSinkBuilder::style_mode
154    /// [via_print_macro]: StdStreamSinkBuilder::via_print_macro
155    #[must_use]
156    pub fn builder() -> StdStreamSinkBuilder<()> {
157        StdStreamSinkBuilder {
158            prop: SinkProp::default(),
159            std_stream: (),
160            style_mode: StyleMode::Auto,
161            via_print_macro: cfg!(feature = "std-stream-captured"),
162        }
163    }
164
165    /// Constructs a `StdStreamSink`.
166    #[deprecated(
167        since = "0.3.0",
168        note = "it may be removed in the future, use `StdStreamSink::builder()` instead"
169    )]
170    #[must_use]
171    pub fn new(std_stream: StdStream, style_mode: StyleMode) -> StdStreamSink {
172        Self::builder()
173            .std_stream(std_stream)
174            .style_mode(style_mode)
175            .build()
176            .unwrap()
177    }
178
179    /// Sets the style of the specified log level.
180    pub fn set_style(&mut self, level: Level, style: Style) {
181        self.level_styles.set_style(level, style);
182    }
183
184    /// Sets the style mode.
185    pub fn set_style_mode(&mut self, style_mode: StyleMode) {
186        self.should_render_style = Self::should_render_style(style_mode, self.std_stream);
187    }
188
189    #[must_use]
190    fn should_render_style(style_mode: StyleMode, stream: StdStream) -> bool {
191        let is_terminal = match stream {
192            StdStream::Stdout => io::stdout().is_terminal(),
193            StdStream::Stderr => io::stderr().is_terminal(),
194        };
195
196        match style_mode {
197            StyleMode::Always => true,
198            StyleMode::Auto => is_terminal && enable_ansi_escape_sequences(),
199            StyleMode::Never => false,
200        }
201    }
202}
203
204impl GetSinkProp for StdStreamSink {
205    fn prop(&self) -> &SinkProp {
206        &self.prop
207    }
208}
209
210impl Sink for StdStreamSink {
211    fn log(&self, record: &Record) -> Result<()> {
212        let mut string_buf = StringBuf::new();
213        let mut ctx = FormatterContext::new();
214        self.prop
215            .formatter()
216            .format(record, &mut string_buf, &mut ctx)?;
217
218        if !self.via_print_macro {
219            self.log_write(record, &string_buf, &ctx, self.std_stream.via_write())
220        } else {
221            self.log_write(record, &string_buf, &ctx, self.std_stream.via_macro())
222        }
223    }
224
225    fn flush(&self) -> Result<()> {
226        if !self.via_print_macro {
227            self.std_stream.via_write().flush()
228        } else {
229            self.std_stream.via_macro().flush()
230        }
231        .map_err(Error::FlushBuffer)
232    }
233}
234
235impl StdStreamSink {
236    fn log_write<O: Write, E: Write>(
237        &self,
238        record: &Record,
239        string_buf: &StringBuf,
240        ctx: &FormatterContext<'_>,
241        mut dest: StdStreamDest<O, E>,
242    ) -> Result<()>
243    where
244        StdStreamDest<O, E>: Write,
245    {
246        (|| {
247            // TODO: Simplify the if block when our MSRV reaches let-chain support.
248            if self.should_render_style {
249                if let Some(style_range) = ctx.style_range() {
250                    let style = self.level_styles.style(record.level());
251
252                    dest.write_all(string_buf[..style_range.start].as_bytes())?;
253                    style.write_start(&mut dest)?;
254                    dest.write_all(string_buf[style_range.start..style_range.end].as_bytes())?;
255                    style.write_end(&mut dest)?;
256                    dest.write_all(string_buf[style_range.end..].as_bytes())?;
257                } else {
258                    dest.write_all(string_buf.as_bytes())?;
259                }
260            } else {
261                dest.write_all(string_buf.as_bytes())?;
262            }
263
264            Ok(())
265        })()
266        .map_err(Error::WriteRecord)?;
267
268        // stderr is not buffered, so we don't need to flush it.
269        // https://doc.rust-lang.org/std/io/fn.stderr.html
270        if let StdStream::Stdout = self.std_stream {
271            dest.flush().map_err(Error::FlushBuffer)?;
272        }
273
274        Ok(())
275    }
276}
277
278// --------------------------------------------------
279
280/// #
281#[doc = include_str!("../include/doc/generic-builder-note.md")]
282pub struct StdStreamSinkBuilder<ArgSS> {
283    prop: SinkProp,
284    std_stream: ArgSS,
285    style_mode: StyleMode,
286    via_print_macro: bool,
287}
288
289impl<ArgSS> StdStreamSinkBuilder<ArgSS> {
290    /// Specifies the target standard stream as stdout.
291    ///
292    /// This is equivalent to `std_stream(StdStream::Stdout)`.
293    #[must_use]
294    pub fn stdout(self) -> StdStreamSinkBuilder<StdStream> {
295        self.std_stream(StdStream::Stdout)
296    }
297
298    /// Specifies the target standard stream as stderr.
299    ///
300    /// This is equivalent to `std_stream(StdStream::Stderr)`.
301    #[must_use]
302    pub fn stderr(self) -> StdStreamSinkBuilder<StdStream> {
303        self.std_stream(StdStream::Stderr)
304    }
305
306    /// Specifies the target standard stream.
307    ///
308    /// This parameter is **required**.
309    #[must_use]
310    pub fn std_stream(self, std_stream: StdStream) -> StdStreamSinkBuilder<StdStream> {
311        StdStreamSinkBuilder {
312            prop: self.prop,
313            std_stream,
314            style_mode: self.style_mode,
315            via_print_macro: self.via_print_macro,
316        }
317    }
318
319    /// Specifies the style mode.
320    ///
321    /// This parameter is **optional**, and defaults to [StyleMode::Auto].
322    #[must_use]
323    pub fn style_mode(mut self, style_mode: StyleMode) -> Self {
324        self.style_mode = style_mode;
325        self
326    }
327
328    /// Specifies to use `print!` and `eprint!` macros for output.
329    ///
330    /// If enabled, the sink will use [`print!`] and [`eprint!`] macros instead
331    /// of [`io::Write`] trait with [`io::stdout`] and [`io::stderr`] to output
332    /// logs. This is useful if you want the logs to be [captured] by `cargo
333    /// test` and `cargo bench`.
334    ///
335    /// This parameter is **optional**, and defaults to `false`, or defaults to
336    /// `true` if feature gate `std-stream-captured` is enabled.
337    ///
338    /// A convienient way to enable it for `cargo test` and `cargo bench` is to
339    /// add the following lines to your `Cargo.toml`:
340    ///
341    /// ```toml
342    /// # Note that it's not [dependencies]
343    ///
344    /// [dev-dependencies]
345    /// spdlog-rs = { version = "...", features = ["std-stream-captured"] }
346    /// ```
347    ///
348    /// [captured]: https://doc.rust-lang.org/book/ch11-02-running-tests.html#showing-function-output
349    #[must_use]
350    pub fn via_print_macro(mut self) -> Self {
351        self.via_print_macro = true;
352        self
353    }
354
355    // Prop
356    //
357
358    /// Specifies a log level filter.
359    ///
360    /// This parameter is **optional**, and defaults to [`LevelFilter::All`].
361    #[must_use]
362    pub fn level_filter(self, level_filter: LevelFilter) -> Self {
363        self.prop.set_level_filter(level_filter);
364        self
365    }
366
367    /// Specifies a formatter.
368    ///
369    /// This parameter is **optional**, and defaults to [`FullFormatter`].
370    ///
371    /// [`FullFormatter`]: crate::formatter::FullFormatter
372    #[must_use]
373    pub fn formatter<F>(self, formatter: F) -> Self
374    where
375        F: Formatter + 'static,
376    {
377        self.prop.set_formatter(formatter);
378        self
379    }
380
381    /// Specifies an error handler.
382    ///
383    /// This parameter is **optional**, and defaults to
384    /// [`ErrorHandler::default()`].
385    #[must_use]
386    pub fn error_handler<F: Into<ErrorHandler>>(self, handler: F) -> Self {
387        self.prop.set_error_handler(handler);
388        self
389    }
390}
391
392impl StdStreamSinkBuilder<()> {
393    #[doc(hidden)]
394    #[deprecated(note = "\n\n\
395        builder compile-time error:\n\
396        - missing required parameter `std_stream`\n\n\
397    ")]
398    pub fn build(self, _: Infallible) {}
399
400    #[doc(hidden)]
401    #[deprecated(note = "\n\n\
402        builder compile-time error:\n\
403        - missing required parameter `std_stream`\n\n\
404    ")]
405    pub fn build_arc(self, _: Infallible) {}
406}
407
408impl StdStreamSinkBuilder<StdStream> {
409    /// Builds a [`StdStreamSink`].
410    pub fn build(self) -> Result<StdStreamSink> {
411        Ok(StdStreamSink {
412            prop: self.prop,
413            via_print_macro: self.via_print_macro,
414            std_stream: self.std_stream,
415            should_render_style: StdStreamSink::should_render_style(
416                self.style_mode,
417                self.std_stream,
418            ),
419            level_styles: LevelStyles::default(),
420        })
421    }
422
423    /// Builds a `Arc<StdStreamSink>`.
424    ///
425    /// This is a shorthand method for `.build().map(Arc::new)`.
426    pub fn build_arc(self) -> Result<Arc<StdStreamSink>> {
427        self.build().map(Arc::new)
428    }
429}
430
431// --------------------------------------------------
432#[cfg(windows)]
433#[must_use]
434fn enable_ansi_escape_sequences() -> bool {
435    #[must_use]
436    fn enable() -> bool {
437        use winapi::um::{
438            consoleapi::{GetConsoleMode, SetConsoleMode},
439            handleapi::INVALID_HANDLE_VALUE,
440            processenv::GetStdHandle,
441            winbase::STD_OUTPUT_HANDLE,
442            wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING,
443        };
444
445        let stdout_handle = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) };
446        if stdout_handle == INVALID_HANDLE_VALUE {
447            return false;
448        }
449
450        let mut original_mode = 0;
451        if unsafe { GetConsoleMode(stdout_handle, &mut original_mode) } == 0 {
452            return false;
453        }
454
455        let new_mode = original_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
456        (unsafe { SetConsoleMode(stdout_handle, new_mode) }) != 0
457    }
458
459    use once_cell::sync::OnceCell;
460
461    static INIT: OnceCell<bool> = OnceCell::new();
462
463    *INIT.get_or_init(enable)
464}
465
466#[cfg(not(windows))]
467#[must_use]
468fn enable_ansi_escape_sequences() -> bool {
469    true
470}