sloggers/
terminal.rs

1//! Terminal logger.
2use crate::build::BuilderCommon;
3#[cfg(feature = "slog-kvfilter")]
4use crate::types::KVFilterParameters;
5use crate::types::{Format, OverflowStrategy, Severity, SourceLocation, TimeZone};
6use crate::{misc, BuildWithCustomFormat};
7use crate::{Build, Config, Result};
8use serde::{Deserialize, Serialize};
9use slog::{Drain, Logger};
10use slog_term::{self, CompactFormat, FullFormat, PlainDecorator, TermDecorator};
11use std::fmt::Debug;
12use std::io;
13
14/// A logger builder which build loggers that output log records to the terminal.
15///
16/// The resulting logger will work asynchronously (the default channel size is 1024).
17#[derive(Debug)]
18pub struct TerminalLoggerBuilder {
19    common: BuilderCommon,
20    format: Format,
21    timezone: TimeZone,
22    destination: Destination,
23}
24impl TerminalLoggerBuilder {
25    /// Makes a new `TerminalLoggerBuilder` instance.
26    pub fn new() -> Self {
27        TerminalLoggerBuilder {
28            common: BuilderCommon::default(),
29            format: Format::default(),
30            timezone: TimeZone::default(),
31            destination: Destination::default(),
32        }
33    }
34
35    /// Sets the format of log records.
36    pub fn format(&mut self, format: Format) -> &mut Self {
37        self.format = format;
38        self
39    }
40
41    /// Sets the source code location type this logger will use.
42    pub fn source_location(&mut self, source_location: SourceLocation) -> &mut Self {
43        self.common.source_location = source_location;
44        self
45    }
46
47    /// Sets the overflow strategy for the logger.
48    pub fn overflow_strategy(&mut self, overflow_strategy: OverflowStrategy) -> &mut Self {
49        self.common.overflow_strategy = overflow_strategy;
50        self
51    }
52
53    /// Sets the time zone which this logger will use.
54    pub fn timezone(&mut self, timezone: TimeZone) -> &mut Self {
55        self.timezone = timezone;
56        self
57    }
58
59    /// Sets the destination to which log records will be outputted.
60    pub fn destination(&mut self, destination: Destination) -> &mut Self {
61        self.destination = destination;
62        self
63    }
64
65    /// Sets the log level of this logger.
66    pub fn level(&mut self, severity: Severity) -> &mut Self {
67        self.common.level = severity;
68        self
69    }
70
71    /// Sets the size of the asynchronous channel of this logger.
72    pub fn channel_size(&mut self, channel_size: usize) -> &mut Self {
73        self.common.channel_size = channel_size;
74        self
75    }
76
77    /// Sets [`KVFilter`].
78    ///
79    /// [`KVFilter`]: https://docs.rs/slog-kvfilter/0.6/slog_kvfilter/struct.KVFilter.html
80    #[cfg(feature = "slog-kvfilter")]
81    pub fn kvfilter(&mut self, parameters: KVFilterParameters) -> &mut Self {
82        self.common.kvfilterparameters = Some(parameters);
83        self
84    }
85}
86impl Default for TerminalLoggerBuilder {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91impl Build for TerminalLoggerBuilder {
92    fn build(&self) -> Result<Logger> {
93        let decorator = self.destination.to_decorator();
94        let timestamp = misc::timezone_to_timestamp_fn(self.timezone);
95        let logger = match self.format {
96            Format::Full => {
97                let format = FullFormat::new(decorator).use_custom_timestamp(timestamp);
98                self.common.build_with_drain(format.build())
99            }
100            Format::Compact => {
101                let format = CompactFormat::new(decorator).use_custom_timestamp(timestamp);
102                self.common.build_with_drain(format.build())
103            }
104            #[cfg(feature = "json")]
105            Format::Json => match self.destination {
106                Destination::Stdout => self.common.build_with_drain(
107                    slog_json::Json::new(std::io::stdout())
108                        .set_flush(true)
109                        .add_default_keys()
110                        .build(),
111                ),
112                Destination::Stderr => self.common.build_with_drain(
113                    slog_json::Json::new(std::io::stderr())
114                        .set_flush(true)
115                        .add_default_keys()
116                        .build(),
117                ),
118            },
119        };
120        Ok(logger)
121    }
122}
123impl BuildWithCustomFormat for TerminalLoggerBuilder {
124    type Decorator = TerminalLoggerDecorator;
125
126    fn build_with_custom_format<F, D>(&self, f: F) -> Result<Logger>
127    where
128        F: FnOnce(Self::Decorator) -> Result<D>,
129        D: Drain + Send + 'static,
130        D::Err: Debug,
131    {
132        let decorator = TerminalLoggerDecorator(self.destination.to_decorator());
133        let drain = track!(f(decorator))?;
134        Ok(self.common.build_with_drain(drain))
135    }
136}
137
138/// [`slog_term::Decorator`] implementation for [`TerminalLoggerBuilder`].
139pub struct TerminalLoggerDecorator(Decorator);
140
141impl slog_term::Decorator for TerminalLoggerDecorator {
142    fn with_record<F>(
143        &self,
144        record: &slog::Record,
145        logger_values: &slog::OwnedKVList,
146        f: F,
147    ) -> io::Result<()>
148    where
149        F: FnOnce(&mut dyn slog_term::RecordDecorator) -> io::Result<()>,
150    {
151        self.0.with_record(record, logger_values, f)
152    }
153}
154
155/// The destination to which log records will be outputted.
156///
157/// # Examples
158///
159/// The default value:
160///
161/// ```
162/// use sloggers::terminal::Destination;
163///
164/// assert_eq!(Destination::default(), Destination::Stderr);
165/// ```
166#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
167#[serde(rename_all = "lowercase")]
168pub enum Destination {
169    /// Standard output.
170    Stdout,
171
172    /// Standard error.
173    #[default]
174    Stderr,
175}
176impl Destination {
177    fn to_decorator(self) -> Decorator {
178        let maybe_term_decorator = match self {
179            Destination::Stdout => TermDecorator::new().stdout().try_build(),
180            Destination::Stderr => TermDecorator::new().stderr().try_build(),
181        };
182        maybe_term_decorator
183            .map(Decorator::Term)
184            .unwrap_or_else(|| match self {
185                Destination::Stdout => Decorator::PlainStdout(PlainDecorator::new(io::stdout())),
186                Destination::Stderr => Decorator::PlainStderr(PlainDecorator::new(io::stderr())),
187            })
188    }
189}
190
191enum Decorator {
192    Term(TermDecorator),
193    PlainStdout(PlainDecorator<io::Stdout>),
194    PlainStderr(PlainDecorator<io::Stderr>),
195}
196impl slog_term::Decorator for Decorator {
197    fn with_record<F>(
198        &self,
199        record: &slog::Record,
200        logger_values: &slog::OwnedKVList,
201        f: F,
202    ) -> io::Result<()>
203    where
204        F: FnOnce(&mut dyn slog_term::RecordDecorator) -> io::Result<()>,
205    {
206        match *self {
207            Decorator::Term(ref d) => d.with_record(record, logger_values, f),
208            Decorator::PlainStdout(ref d) => d.with_record(record, logger_values, f),
209            Decorator::PlainStderr(ref d) => d.with_record(record, logger_values, f),
210        }
211    }
212}
213
214/// The configuration of `TerminalLoggerBuilder`.
215#[derive(Debug, Default, Clone, Serialize, Deserialize)]
216#[non_exhaustive]
217pub struct TerminalLoggerConfig {
218    /// Log level.
219    #[serde(default)]
220    pub level: Severity,
221
222    /// Log record format.
223    #[serde(default)]
224    pub format: Format,
225
226    /// Source code location
227    #[serde(default)]
228    pub source_location: SourceLocation,
229
230    /// Time Zone.
231    #[serde(default)]
232    pub timezone: TimeZone,
233
234    /// Output destination.
235    #[serde(default)]
236    pub destination: Destination,
237
238    /// Asynchronous channel size
239    #[serde(default = "default_channel_size")]
240    pub channel_size: usize,
241
242    /// Whether to drop logs on overflow.
243    ///
244    /// The possible values are `drop`, `drop_and_report`, or `block`.
245    ///
246    /// The default value is `drop_and_report`.
247    #[serde(default)]
248    pub overflow_strategy: OverflowStrategy,
249}
250impl TerminalLoggerConfig {
251    /// Creates a new `TerminalLoggerConfig` with default settings.
252    pub fn new() -> Self {
253        Default::default()
254    }
255}
256impl Config for TerminalLoggerConfig {
257    type Builder = TerminalLoggerBuilder;
258    fn try_to_builder(&self) -> Result<Self::Builder> {
259        let mut builder = TerminalLoggerBuilder::new();
260        builder.level(self.level);
261        builder.format(self.format);
262        builder.source_location(self.source_location);
263        builder.timezone(self.timezone);
264        builder.destination(self.destination);
265        builder.channel_size(self.channel_size);
266        builder.overflow_strategy(self.overflow_strategy);
267        Ok(builder)
268    }
269}
270
271fn default_channel_size() -> usize {
272    1024
273}