fern/
log_impl.rs

1use std::{
2    borrow::Cow,
3    collections::HashMap,
4    fmt, fs,
5    io::{self, BufWriter, Write},
6    sync::{mpsc, Arc, Mutex},
7};
8
9#[cfg(feature = "date-based")]
10use std::{
11    ffi::OsString,
12    fs::OpenOptions,
13    path::{Path, PathBuf},
14};
15
16use log::{self, Log};
17
18use crate::{Filter, Formatter};
19
20#[cfg(all(not(windows), feature = "syslog-4"))]
21use crate::{Syslog4Rfc3164Logger, Syslog4Rfc5424Logger, Syslog4TransformFn};
22#[cfg(all(not(windows), feature = "syslog-6"))]
23use crate::{Syslog6Rfc3164Logger, Syslog6Rfc5424Logger, Syslog6TransformFn};
24#[cfg(all(not(windows), feature = "syslog-7"))]
25use crate::{Syslog7Rfc3164Logger, Syslog7Rfc5424Logger, Syslog7TransformFn};
26
27pub enum LevelConfiguration {
28    JustDefault,
29    Minimal(Vec<(Cow<'static, str>, log::LevelFilter)>),
30    Many(HashMap<Cow<'static, str>, log::LevelFilter>),
31}
32
33pub struct Dispatch {
34    pub output: Vec<Output>,
35    pub default_level: log::LevelFilter,
36    pub levels: LevelConfiguration,
37    pub format: Option<Box<Formatter>>,
38    pub filters: Vec<Box<Filter>>,
39}
40
41/// Callback struct for use within a formatter closure
42///
43/// Callbacks are used for formatting in order to allow usage of
44/// [`std::fmt`]-based formatting without the allocation of the formatted
45/// result which would be required to return it.
46///
47/// Example usage:
48///
49/// ```
50/// fern::Dispatch::new().format(|callback: fern::FormatCallback, message, record| {
51///     callback.finish(format_args!("[{}] {}", record.level(), message))
52/// })
53/// # ;
54/// ```
55///
56/// [`std::fmt`]: https://doc.rust-lang.org/std/fmt/index.html
57#[must_use = "format callback must be used for log to process correctly"]
58pub struct FormatCallback<'a>(InnerFormatCallback<'a>);
59
60struct InnerFormatCallback<'a>(&'a mut bool, &'a Dispatch, &'a log::Record<'a>);
61
62pub enum Output {
63    Stdout(Stdout),
64    Stderr(Stderr),
65    File(File),
66    Sender(Sender),
67    #[cfg(all(not(windows), feature = "syslog-3"))]
68    Syslog3(Syslog3),
69    #[cfg(all(not(windows), feature = "syslog-4"))]
70    Syslog4Rfc3164(Syslog4Rfc3164),
71    #[cfg(all(not(windows), feature = "syslog-4"))]
72    Syslog4Rfc5424(Syslog4Rfc5424),
73    #[cfg(all(not(windows), feature = "syslog-6"))]
74    Syslog6Rfc3164(Syslog6Rfc3164),
75    #[cfg(all(not(windows), feature = "syslog-6"))]
76    Syslog6Rfc5424(Syslog6Rfc5424),
77    #[cfg(all(not(windows), feature = "syslog-7"))]
78    Syslog7Rfc3164(Syslog7Rfc3164),
79    #[cfg(all(not(windows), feature = "syslog-7"))]
80    Syslog7Rfc5424(Syslog7Rfc5424),
81    Dispatch(Dispatch),
82    SharedDispatch(Arc<Dispatch>),
83    OtherBoxed(Box<dyn Log>),
84    OtherStatic(&'static dyn Log),
85    Panic(Panic),
86    Writer(Writer),
87    #[cfg(feature = "date-based")]
88    DateBased(DateBased),
89    #[cfg(all(not(windows), feature = "reopen-03"))]
90    Reopen(Reopen),
91    #[cfg(all(not(windows), feature = "reopen-1"))]
92    Reopen1(Reopen1),
93}
94
95pub struct Stdout {
96    pub stream: io::Stdout,
97    pub line_sep: Cow<'static, str>,
98}
99
100pub struct Stderr {
101    pub stream: io::Stderr,
102    pub line_sep: Cow<'static, str>,
103}
104
105pub struct File {
106    pub stream: Mutex<BufWriter<fs::File>>,
107    pub line_sep: Cow<'static, str>,
108}
109
110pub struct Sender {
111    pub stream: Mutex<mpsc::Sender<String>>,
112    pub line_sep: Cow<'static, str>,
113}
114
115pub struct Writer {
116    pub stream: Mutex<Box<dyn Write + Send>>,
117    pub line_sep: Cow<'static, str>,
118}
119
120#[cfg(all(not(windows), feature = "reopen-03"))]
121pub struct Reopen {
122    pub stream: Mutex<reopen03::Reopen<fs::File>>,
123    pub line_sep: Cow<'static, str>,
124}
125
126#[cfg(all(not(windows), feature = "reopen-1"))]
127pub struct Reopen1 {
128    pub stream: Mutex<reopen1::Reopen<fs::File>>,
129    pub line_sep: Cow<'static, str>,
130}
131
132#[cfg(all(not(windows), feature = "syslog-3"))]
133pub struct Syslog3 {
134    pub inner: syslog3::Logger,
135}
136
137#[cfg(all(not(windows), feature = "syslog-4"))]
138pub struct Syslog4Rfc3164 {
139    pub inner: Mutex<Syslog4Rfc3164Logger>,
140}
141
142#[cfg(all(not(windows), feature = "syslog-4"))]
143pub struct Syslog4Rfc5424 {
144    pub inner: Mutex<Syslog4Rfc5424Logger>,
145    pub transform: Box<Syslog4TransformFn>,
146}
147
148#[cfg(all(not(windows), feature = "syslog-6"))]
149pub struct Syslog6Rfc3164 {
150    pub inner: Mutex<Syslog6Rfc3164Logger>,
151}
152
153#[cfg(all(not(windows), feature = "syslog-6"))]
154pub struct Syslog6Rfc5424 {
155    pub inner: Mutex<Syslog6Rfc5424Logger>,
156    pub transform: Box<Syslog6TransformFn>,
157}
158
159#[cfg(all(not(windows), feature = "syslog-7"))]
160pub struct Syslog7Rfc3164 {
161    pub inner: Mutex<Syslog7Rfc3164Logger>,
162}
163
164#[cfg(all(not(windows), feature = "syslog-7"))]
165pub struct Syslog7Rfc5424 {
166    pub inner: Mutex<Syslog7Rfc5424Logger>,
167    pub transform: Box<Syslog7TransformFn>,
168}
169
170pub struct Panic;
171
172pub struct Null;
173
174/// File logger with a dynamic time-based name.
175#[derive(Debug)]
176#[cfg(feature = "date-based")]
177pub struct DateBased {
178    pub config: DateBasedConfig,
179    pub state: Mutex<DateBasedState>,
180}
181
182#[derive(Debug)]
183#[cfg(feature = "date-based")]
184pub enum ConfiguredTimezone {
185    Local,
186    Utc,
187}
188
189#[derive(Debug)]
190#[cfg(feature = "date-based")]
191pub struct DateBasedConfig {
192    pub line_sep: Cow<'static, str>,
193    /// This is a Path not an str so it can hold invalid UTF8 paths correctly.
194    pub file_prefix: PathBuf,
195    pub file_suffix: Cow<'static, str>,
196    pub timezone: ConfiguredTimezone,
197}
198
199#[derive(Debug)]
200#[cfg(feature = "date-based")]
201pub struct DateBasedState {
202    pub current_suffix: String,
203    pub file_stream: Option<BufWriter<fs::File>>,
204}
205
206#[cfg(feature = "date-based")]
207impl DateBasedState {
208    pub fn new(current_suffix: String, file_stream: Option<fs::File>) -> Self {
209        DateBasedState {
210            current_suffix,
211            file_stream: file_stream.map(BufWriter::new),
212        }
213    }
214
215    pub fn replace_file(&mut self, new_suffix: String, new_file: Option<fs::File>) {
216        if let Some(mut old) = self.file_stream.take() {
217            let _ = old.flush();
218        }
219        self.current_suffix = new_suffix;
220        self.file_stream = new_file.map(BufWriter::new)
221    }
222}
223
224#[cfg(feature = "date-based")]
225impl DateBasedConfig {
226    pub fn new(
227        line_sep: Cow<'static, str>,
228        file_prefix: PathBuf,
229        file_suffix: Cow<'static, str>,
230        timezone: ConfiguredTimezone,
231    ) -> Self {
232        DateBasedConfig {
233            line_sep,
234            file_prefix,
235            file_suffix,
236            timezone,
237        }
238    }
239
240    pub fn compute_current_suffix(&self) -> String {
241        match self.timezone {
242            ConfiguredTimezone::Utc => chrono::Utc::now().format(&self.file_suffix).to_string(),
243            ConfiguredTimezone::Local => chrono::Local::now().format(&self.file_suffix).to_string(),
244        }
245    }
246
247    pub fn compute_file_path(&self, suffix: &str) -> PathBuf {
248        let mut path = OsString::from(&*self.file_prefix);
249        // use the OsString::push method, not PathBuf::push which would add a path
250        // separator
251        path.push(suffix);
252        path.into()
253    }
254
255    pub fn open_log_file(path: &Path) -> io::Result<fs::File> {
256        OpenOptions::new().create(true).append(true).open(path)
257    }
258
259    pub fn open_current_log_file(&self, suffix: &str) -> io::Result<fs::File> {
260        Self::open_log_file(&self.compute_file_path(suffix))
261    }
262}
263
264impl From<Vec<(Cow<'static, str>, log::LevelFilter)>> for LevelConfiguration {
265    fn from(mut levels: Vec<(Cow<'static, str>, log::LevelFilter)>) -> Self {
266        // Benchmarked separately: https://gist.github.com/daboross/976978d8200caf86e02acb6805961195
267        // Use Vec if there are fewer than 15 items, HashMap if there are more than 15.
268        match levels.len() {
269            0 => LevelConfiguration::JustDefault,
270            x if x > 15 => LevelConfiguration::Many(levels.into_iter().collect()),
271            _ => {
272                levels.shrink_to_fit();
273                LevelConfiguration::Minimal(levels)
274            }
275        }
276    }
277}
278
279impl LevelConfiguration {
280    // inline since we use it literally once.
281    #[inline]
282    fn find_module(&self, module: &str) -> Option<log::LevelFilter> {
283        match *self {
284            LevelConfiguration::JustDefault => None,
285            _ => {
286                if let Some(level) = self.find_exact(module) {
287                    return Some(level);
288                }
289
290                // The manual for loop here lets us just iterate over the module string once
291                // while still finding each sub-module. For the module string
292                // "hyper::http::h1", this loop will test first "hyper::http"
293                // then "hyper".
294                let mut last_char_colon = false;
295
296                for (index, ch) in module.char_indices().rev() {
297                    if last_char_colon {
298                        last_char_colon = false;
299                        if ch == ':' {
300                            let sub_module = &module[0..index];
301
302                            if let Some(level) = self.find_exact(sub_module) {
303                                return Some(level);
304                            }
305                        }
306                    } else if ch == ':' {
307                        last_char_colon = true;
308                    }
309                }
310
311                None
312            }
313        }
314    }
315
316    fn find_exact(&self, module: &str) -> Option<log::LevelFilter> {
317        match *self {
318            LevelConfiguration::JustDefault => None,
319            LevelConfiguration::Minimal(ref levels) => levels
320                .iter()
321                .find(|(test_module, _)| test_module == module)
322                .map(|(_, level)| *level),
323            LevelConfiguration::Many(ref levels) => levels.get(module).cloned(),
324        }
325    }
326}
327
328impl Log for Output {
329    fn enabled(&self, metadata: &log::Metadata) -> bool {
330        match *self {
331            Output::Stdout(ref s) => s.enabled(metadata),
332            Output::Stderr(ref s) => s.enabled(metadata),
333            Output::File(ref s) => s.enabled(metadata),
334            Output::Sender(ref s) => s.enabled(metadata),
335            Output::Dispatch(ref s) => s.enabled(metadata),
336            Output::SharedDispatch(ref s) => s.enabled(metadata),
337            Output::OtherBoxed(ref s) => s.enabled(metadata),
338            Output::OtherStatic(ref s) => s.enabled(metadata),
339            #[cfg(all(not(windows), feature = "syslog-3"))]
340            Output::Syslog3(ref s) => s.enabled(metadata),
341            #[cfg(all(not(windows), feature = "syslog-4"))]
342            Output::Syslog4Rfc3164(ref s) => s.enabled(metadata),
343            #[cfg(all(not(windows), feature = "syslog-4"))]
344            Output::Syslog4Rfc5424(ref s) => s.enabled(metadata),
345            #[cfg(all(not(windows), feature = "syslog-6"))]
346            Output::Syslog6Rfc3164(ref s) => s.enabled(metadata),
347            #[cfg(all(not(windows), feature = "syslog-6"))]
348            Output::Syslog6Rfc5424(ref s) => s.enabled(metadata),
349            #[cfg(all(not(windows), feature = "syslog-7"))]
350            Output::Syslog7Rfc3164(ref s) => s.enabled(metadata),
351            #[cfg(all(not(windows), feature = "syslog-7"))]
352            Output::Syslog7Rfc5424(ref s) => s.enabled(metadata),
353            Output::Panic(ref s) => s.enabled(metadata),
354            Output::Writer(ref s) => s.enabled(metadata),
355            #[cfg(feature = "date-based")]
356            Output::DateBased(ref s) => s.enabled(metadata),
357            #[cfg(all(not(windows), feature = "reopen-03"))]
358            Output::Reopen(ref s) => s.enabled(metadata),
359            #[cfg(all(not(windows), feature = "reopen-1"))]
360            Output::Reopen1(ref s) => s.enabled(metadata),
361        }
362    }
363
364    fn log(&self, record: &log::Record) {
365        match *self {
366            Output::Stdout(ref s) => s.log(record),
367            Output::Stderr(ref s) => s.log(record),
368            Output::File(ref s) => s.log(record),
369            Output::Sender(ref s) => s.log(record),
370            Output::Dispatch(ref s) => s.log(record),
371            Output::SharedDispatch(ref s) => s.log(record),
372            Output::OtherBoxed(ref s) => s.log(record),
373            Output::OtherStatic(ref s) => s.log(record),
374            #[cfg(all(not(windows), feature = "syslog-3"))]
375            Output::Syslog3(ref s) => s.log(record),
376            #[cfg(all(not(windows), feature = "syslog-4"))]
377            Output::Syslog4Rfc3164(ref s) => s.log(record),
378            #[cfg(all(not(windows), feature = "syslog-4"))]
379            Output::Syslog4Rfc5424(ref s) => s.log(record),
380            #[cfg(all(not(windows), feature = "syslog-6"))]
381            Output::Syslog6Rfc3164(ref s) => s.log(record),
382            #[cfg(all(not(windows), feature = "syslog-6"))]
383            Output::Syslog6Rfc5424(ref s) => s.log(record),
384            #[cfg(all(not(windows), feature = "syslog-7"))]
385            Output::Syslog7Rfc3164(ref s) => s.log(record),
386            #[cfg(all(not(windows), feature = "syslog-7"))]
387            Output::Syslog7Rfc5424(ref s) => s.log(record),
388            Output::Panic(ref s) => s.log(record),
389            Output::Writer(ref s) => s.log(record),
390            #[cfg(feature = "date-based")]
391            Output::DateBased(ref s) => s.log(record),
392            #[cfg(all(not(windows), feature = "reopen-03"))]
393            Output::Reopen(ref s) => s.log(record),
394            #[cfg(all(not(windows), feature = "reopen-1"))]
395            Output::Reopen1(ref s) => s.log(record),
396        }
397    }
398
399    fn flush(&self) {
400        match *self {
401            Output::Stdout(ref s) => s.flush(),
402            Output::Stderr(ref s) => s.flush(),
403            Output::File(ref s) => s.flush(),
404            Output::Sender(ref s) => s.flush(),
405            Output::Dispatch(ref s) => s.flush(),
406            Output::SharedDispatch(ref s) => s.flush(),
407            Output::OtherBoxed(ref s) => s.flush(),
408            Output::OtherStatic(ref s) => s.flush(),
409            #[cfg(all(not(windows), feature = "syslog-3"))]
410            Output::Syslog3(ref s) => s.flush(),
411            #[cfg(all(not(windows), feature = "syslog-4"))]
412            Output::Syslog4Rfc3164(ref s) => s.flush(),
413            #[cfg(all(not(windows), feature = "syslog-4"))]
414            Output::Syslog4Rfc5424(ref s) => s.flush(),
415            #[cfg(all(not(windows), feature = "syslog-6"))]
416            Output::Syslog6Rfc3164(ref s) => s.flush(),
417            #[cfg(all(not(windows), feature = "syslog-6"))]
418            Output::Syslog6Rfc5424(ref s) => s.flush(),
419            #[cfg(all(not(windows), feature = "syslog-7"))]
420            Output::Syslog7Rfc3164(ref s) => s.flush(),
421            #[cfg(all(not(windows), feature = "syslog-7"))]
422            Output::Syslog7Rfc5424(ref s) => s.flush(),
423            Output::Panic(ref s) => s.flush(),
424            Output::Writer(ref s) => s.flush(),
425            #[cfg(feature = "date-based")]
426            Output::DateBased(ref s) => s.flush(),
427            #[cfg(all(not(windows), feature = "reopen-03"))]
428            Output::Reopen(ref s) => s.flush(),
429            #[cfg(all(not(windows), feature = "reopen-1"))]
430            Output::Reopen1(ref s) => s.flush(),
431        }
432    }
433}
434
435impl Log for Null {
436    fn enabled(&self, _: &log::Metadata) -> bool {
437        false
438    }
439
440    fn log(&self, _: &log::Record) {}
441
442    fn flush(&self) {}
443}
444
445impl Log for Dispatch {
446    fn enabled(&self, metadata: &log::Metadata) -> bool {
447        self.deep_enabled(metadata)
448    }
449
450    fn log(&self, record: &log::Record) {
451        if self.shallow_enabled(record.metadata()) {
452            match self.format {
453                Some(ref format) => {
454                    // flag to ensure the log message is completed even if the formatter doesn't
455                    // complete the callback.
456                    let mut callback_called_flag = false;
457
458                    (format)(
459                        FormatCallback(InnerFormatCallback(
460                            &mut callback_called_flag,
461                            self,
462                            record,
463                        )),
464                        record.args(),
465                        record,
466                    );
467
468                    if !callback_called_flag {
469                        self.finish_logging(record);
470                    }
471                }
472                None => {
473                    self.finish_logging(record);
474                }
475            }
476        }
477    }
478
479    fn flush(&self) {
480        for log in &self.output {
481            log.flush();
482        }
483    }
484}
485
486impl Dispatch {
487    fn finish_logging(&self, record: &log::Record) {
488        for log in &self.output {
489            log.log(record);
490        }
491    }
492
493    /// Check whether this log's filters prevent the given log from happening.
494    fn shallow_enabled(&self, metadata: &log::Metadata) -> bool {
495        metadata.level()
496            <= self
497                .levels
498                .find_module(metadata.target())
499                .unwrap_or(self.default_level)
500            && self.filters.iter().all(|f| f(metadata))
501    }
502
503    /// Check whether a log with the given metadata would eventually end up
504    /// outputting something.
505    ///
506    /// This is recursive, and checks children.
507    fn deep_enabled(&self, metadata: &log::Metadata) -> bool {
508        self.shallow_enabled(metadata) && self.output.iter().any(|l| l.enabled(metadata))
509    }
510}
511
512impl<'a> FormatCallback<'a> {
513    /// Complete the formatting call that this FormatCallback was created for.
514    ///
515    /// This will call the rest of the logging chain using the given formatted
516    /// message as the new payload message.
517    ///
518    /// Example usage:
519    ///
520    /// ```
521    /// # fern::Dispatch::new()
522    /// # .format(|callback: fern::FormatCallback, message, record| {
523    /// callback.finish(format_args!("[{}] {}", record.level(), message))
524    /// # })
525    /// # .into_log();
526    /// ```
527    ///
528    /// See [`format_args!`].
529    ///
530    /// [`format_args!`]: https://doc.rust-lang.org/std/macro.format_args.html
531    pub fn finish(self, formatted_message: fmt::Arguments) {
532        let FormatCallback(InnerFormatCallback(callback_called_flag, dispatch, record)) = self;
533
534        // let the dispatch know that we did in fact get called.
535        *callback_called_flag = true;
536
537        // NOTE: This needs to be updated whenever new things are added to
538        // `log::Record`.
539        let new_record = log::RecordBuilder::new()
540            .args(formatted_message)
541            .metadata(record.metadata().clone())
542            .level(record.level())
543            .target(record.target())
544            .module_path(record.module_path())
545            .file(record.file())
546            .line(record.line())
547            .build();
548
549        dispatch.finish_logging(&new_record);
550    }
551}
552
553// No need to write this twice (used for Stdout and Stderr structs)
554macro_rules! std_log_impl {
555    ($ident:ident) => {
556        impl Log for $ident {
557            fn enabled(&self, _: &log::Metadata) -> bool {
558                true
559            }
560
561            fn log(&self, record: &log::Record) {
562                fallback_on_error(record, |record| {
563                    if cfg!(feature = "meta-logging-in-format") {
564                        // Formatting first prevents deadlocks when the process of formatting
565                        // itself is logged. note: this is only ever needed if some
566                        // Debug, Display, or other formatting trait itself is
567                        // logging things too.
568                        let msg = format!("{}{}", record.args(), self.line_sep);
569
570                        write!(self.stream.lock(), "{}", msg)?;
571                    } else {
572                        write!(self.stream.lock(), "{}{}", record.args(), self.line_sep)?;
573                    }
574
575                    Ok(())
576                });
577            }
578
579            fn flush(&self) {
580                let _ = self.stream.lock().flush();
581            }
582        }
583    };
584}
585
586std_log_impl!(Stdout);
587std_log_impl!(Stderr);
588
589macro_rules! writer_log_impl {
590    ($ident:ident) => {
591        impl Log for $ident {
592            fn enabled(&self, _: &log::Metadata) -> bool {
593                true
594            }
595
596            fn log(&self, record: &log::Record) {
597                fallback_on_error(record, |record| {
598                    if cfg!(feature = "meta-logging-in-format") {
599                        // Formatting first prevents deadlocks on file-logging,
600                        // when the process of formatting itself is logged.
601                        // note: this is only ever needed if some Debug, Display, or other
602                        // formatting trait itself is logging.
603                        let msg = format!("{}{}", record.args(), self.line_sep);
604
605                        let mut writer = self.stream.lock().unwrap_or_else(|e| e.into_inner());
606
607                        write!(writer, "{}", msg)?;
608
609                        writer.flush()?;
610                    } else {
611                        let mut writer = self.stream.lock().unwrap_or_else(|e| e.into_inner());
612
613                        write!(writer, "{}{}", record.args(), self.line_sep)?;
614
615                        writer.flush()?;
616                    }
617                    Ok(())
618                });
619            }
620
621            fn flush(&self) {
622                let _ = self
623                    .stream
624                    .lock()
625                    .unwrap_or_else(|e| e.into_inner())
626                    .flush();
627            }
628        }
629    };
630}
631
632writer_log_impl!(File);
633writer_log_impl!(Writer);
634
635#[cfg(all(not(windows), feature = "reopen-03"))]
636writer_log_impl!(Reopen);
637
638#[cfg(all(not(windows), feature = "reopen-1"))]
639writer_log_impl!(Reopen1);
640
641impl Log for Sender {
642    fn enabled(&self, _: &log::Metadata) -> bool {
643        true
644    }
645
646    fn log(&self, record: &log::Record) {
647        fallback_on_error(record, |record| {
648            let msg = format!("{}{}", record.args(), self.line_sep);
649            self.stream
650                .lock()
651                .unwrap_or_else(|e| e.into_inner())
652                .send(msg)?;
653            Ok(())
654        });
655    }
656
657    fn flush(&self) {}
658}
659
660#[cfg(all(
661    not(windows),
662    any(
663        feature = "syslog-3",
664        feature = "syslog-4",
665        feature = "syslog-6",
666        feature = "syslog-7"
667    )
668))]
669macro_rules! send_syslog {
670    ($logger:expr, $level:expr, $message:expr) => {
671        use log::Level;
672        match $level {
673            Level::Error => $logger.err($message)?,
674            Level::Warn => $logger.warning($message)?,
675            Level::Info => $logger.info($message)?,
676            Level::Debug | Level::Trace => $logger.debug($message)?,
677        }
678    };
679}
680
681#[cfg(all(not(windows), feature = "syslog-3"))]
682impl Log for Syslog3 {
683    fn enabled(&self, _: &log::Metadata) -> bool {
684        true
685    }
686
687    fn log(&self, record: &log::Record) {
688        fallback_on_error(record, |record| {
689            let message = record.args();
690            send_syslog!(self.inner, record.level(), message);
691
692            Ok(())
693        });
694    }
695    fn flush(&self) {}
696}
697
698#[cfg(all(not(windows), feature = "syslog-4"))]
699impl Log for Syslog4Rfc3164 {
700    fn enabled(&self, _: &log::Metadata) -> bool {
701        true
702    }
703
704    fn log(&self, record: &log::Record) {
705        fallback_on_error(record, |record| {
706            let message = record.args().to_string();
707            let mut log = self.inner.lock().unwrap_or_else(|e| e.into_inner());
708            send_syslog!(log, record.level(), message);
709
710            Ok(())
711        });
712    }
713    fn flush(&self) {}
714}
715
716#[cfg(all(not(windows), feature = "syslog-4"))]
717impl Log for Syslog4Rfc5424 {
718    fn enabled(&self, _: &log::Metadata) -> bool {
719        true
720    }
721
722    fn log(&self, record: &log::Record) {
723        fallback_on_error(record, |record| {
724            let transformed = (self.transform)(record);
725            let mut log = self.inner.lock().unwrap_or_else(|e| e.into_inner());
726            send_syslog!(log, record.level(), transformed);
727
728            Ok(())
729        });
730    }
731    fn flush(&self) {}
732}
733
734#[cfg(all(not(windows), feature = "syslog-6"))]
735impl Log for Syslog6Rfc3164 {
736    fn enabled(&self, _: &log::Metadata) -> bool {
737        true
738    }
739
740    fn log(&self, record: &log::Record) {
741        fallback_on_error(record, |record| {
742            let message = record.args().to_string();
743            let mut log = self.inner.lock().unwrap_or_else(|e| e.into_inner());
744            send_syslog!(log, record.level(), message);
745
746            Ok(())
747        });
748    }
749    fn flush(&self) {}
750}
751
752#[cfg(all(not(windows), feature = "syslog-6"))]
753impl Log for Syslog6Rfc5424 {
754    fn enabled(&self, _: &log::Metadata) -> bool {
755        true
756    }
757
758    fn log(&self, record: &log::Record) {
759        fallback_on_error(record, |record| {
760            let transformed = (self.transform)(record);
761            let mut log = self.inner.lock().unwrap_or_else(|e| e.into_inner());
762            send_syslog!(log, record.level(), transformed);
763
764            Ok(())
765        });
766    }
767    fn flush(&self) {}
768}
769
770#[cfg(all(not(windows), feature = "syslog-7"))]
771impl Log for Syslog7Rfc3164 {
772    fn enabled(&self, _: &log::Metadata) -> bool {
773        true
774    }
775
776    fn log(&self, record: &log::Record) {
777        fallback_on_error(record, |record| {
778            let message = record.args().to_string();
779            let mut log = self.inner.lock().unwrap_or_else(|e| e.into_inner());
780            send_syslog!(log, record.level(), message);
781
782            Ok(())
783        });
784    }
785    fn flush(&self) {}
786}
787
788#[cfg(all(not(windows), feature = "syslog-7"))]
789impl Log for Syslog7Rfc5424 {
790    fn enabled(&self, _: &log::Metadata) -> bool {
791        true
792    }
793
794    fn log(&self, record: &log::Record) {
795        fallback_on_error(record, |record| {
796            let transformed = (self.transform)(record);
797            let mut log = self.inner.lock().unwrap_or_else(|e| e.into_inner());
798            send_syslog!(log, record.level(), transformed);
799
800            Ok(())
801        });
802    }
803    fn flush(&self) {}
804}
805
806impl Log for Panic {
807    fn enabled(&self, _: &log::Metadata) -> bool {
808        true
809    }
810
811    fn log(&self, record: &log::Record) {
812        panic!("{}", record.args());
813    }
814
815    fn flush(&self) {}
816}
817
818#[cfg(feature = "date-based")]
819impl Log for DateBased {
820    fn enabled(&self, _: &log::Metadata) -> bool {
821        true
822    }
823
824    fn log(&self, record: &log::Record) {
825        fallback_on_error(record, |record| {
826            // Formatting first prevents deadlocks on file-logging,
827            // when the process of formatting itself is logged.
828            // note: this is only ever needed if some Debug, Display, or other
829            // formatting trait itself is logging.
830            #[cfg(feature = "meta-logging-in-format")]
831            let msg = format!("{}{}", record.args(), self.config.line_sep);
832
833            let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
834
835            // check if log needs to be rotated
836            let new_suffix = self.config.compute_current_suffix();
837            if state.file_stream.is_none() || state.current_suffix != new_suffix {
838                let file_open_result = self.config.open_current_log_file(&new_suffix);
839                match file_open_result {
840                    Ok(file) => {
841                        state.replace_file(new_suffix, Some(file));
842                    }
843                    Err(e) => {
844                        state.replace_file(new_suffix, None);
845                        return Err(e.into());
846                    }
847                }
848            }
849
850            // either just initialized writer above, or already errored out.
851            let writer = state.file_stream.as_mut().unwrap();
852
853            #[cfg(feature = "meta-logging-in-format")]
854            write!(writer, "{}", msg)?;
855            #[cfg(not(feature = "meta-logging-in-format"))]
856            write!(writer, "{}{}", record.args(), self.config.line_sep)?;
857
858            writer.flush()?;
859
860            Ok(())
861        });
862    }
863
864    fn flush(&self) {
865        let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
866
867        if let Some(stream) = &mut state.file_stream {
868            let _ = stream.flush();
869        }
870    }
871}
872
873#[inline(always)]
874fn fallback_on_error<F>(record: &log::Record, log_func: F)
875where
876    F: FnOnce(&log::Record) -> Result<(), LogError>,
877{
878    if let Err(error) = log_func(record) {
879        backup_logging(record, &error)
880    }
881}
882
883fn backup_logging(record: &log::Record, error: &LogError) {
884    let second = write!(
885        io::stderr(),
886        "Error performing logging.\
887         \n\tattempted to log: {}\
888         \n\trecord: {:?}\
889         \n\tlogging error: {}",
890        record.args(),
891        record,
892        error
893    );
894
895    if let Err(second_error) = second {
896        panic!(
897            "Error performing stderr logging after error occurred during regular logging.\
898             \n\tattempted to log: {}\
899             \n\trecord: {:?}\
900             \n\tfirst logging error: {}\
901             \n\tstderr error: {}",
902            record.args(),
903            record,
904            error,
905            second_error,
906        );
907    }
908}
909
910#[derive(Debug)]
911enum LogError {
912    Io(io::Error),
913    Send(mpsc::SendError<String>),
914    #[cfg(all(not(windows), feature = "syslog-4"))]
915    Syslog4(syslog4::Error),
916    #[cfg(all(not(windows), feature = "syslog-6"))]
917    Syslog6(syslog6::Error),
918    #[cfg(all(not(windows), feature = "syslog-7"))]
919    Syslog7(syslog7::Error),
920}
921
922impl fmt::Display for LogError {
923    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
924        match *self {
925            LogError::Io(ref e) => write!(f, "{}", e),
926            LogError::Send(ref e) => write!(f, "{}", e),
927            #[cfg(all(not(windows), feature = "syslog-4"))]
928            LogError::Syslog4(ref e) => write!(f, "{}", e),
929            #[cfg(all(not(windows), feature = "syslog-6"))]
930            LogError::Syslog6(ref e) => write!(f, "{}", e),
931            #[cfg(all(not(windows), feature = "syslog-7"))]
932            LogError::Syslog7(ref e) => write!(f, "{}", e),
933        }
934    }
935}
936
937impl From<io::Error> for LogError {
938    fn from(error: io::Error) -> Self {
939        LogError::Io(error)
940    }
941}
942
943impl From<mpsc::SendError<String>> for LogError {
944    fn from(error: mpsc::SendError<String>) -> Self {
945        LogError::Send(error)
946    }
947}
948
949#[cfg(all(not(windows), feature = "syslog-4"))]
950impl From<syslog4::Error> for LogError {
951    fn from(error: syslog4::Error) -> Self {
952        LogError::Syslog4(error)
953    }
954}
955
956#[cfg(all(not(windows), feature = "syslog-6"))]
957impl From<syslog6::Error> for LogError {
958    fn from(error: syslog6::Error) -> Self {
959        LogError::Syslog6(error)
960    }
961}
962
963#[cfg(all(not(windows), feature = "syslog-7"))]
964impl From<syslog7::Error> for LogError {
965    fn from(error: syslog7::Error) -> Self {
966        LogError::Syslog7(error)
967    }
968}
969
970#[cfg(test)]
971mod test {
972    use super::LevelConfiguration;
973    use log::LevelFilter::*;
974
975    #[test]
976    fn test_level_config_find_exact_minimal() {
977        let config = LevelConfiguration::Minimal(
978            vec![("mod1", Info), ("mod2", Debug), ("mod3", Off)]
979                .into_iter()
980                .map(|(k, v)| (k.into(), v))
981                .collect(),
982        );
983
984        assert_eq!(config.find_exact("mod1"), Some(Info));
985        assert_eq!(config.find_exact("mod2"), Some(Debug));
986        assert_eq!(config.find_exact("mod3"), Some(Off));
987    }
988
989    #[test]
990    fn test_level_config_find_exact_many() {
991        let config = LevelConfiguration::Many(
992            vec![("mod1", Info), ("mod2", Debug), ("mod3", Off)]
993                .into_iter()
994                .map(|(k, v)| (k.into(), v))
995                .collect(),
996        );
997
998        assert_eq!(config.find_exact("mod1"), Some(Info));
999        assert_eq!(config.find_exact("mod2"), Some(Debug));
1000        assert_eq!(config.find_exact("mod3"), Some(Off));
1001    }
1002
1003    #[test]
1004    fn test_level_config_simple_hierarchy() {
1005        let config = LevelConfiguration::Minimal(
1006            vec![("mod1", Info), ("mod2::sub_mod", Debug), ("mod3", Off)]
1007                .into_iter()
1008                .map(|(k, v)| (k.into(), v))
1009                .collect(),
1010        );
1011
1012        assert_eq!(config.find_module("mod1::sub_mod"), Some(Info));
1013        assert_eq!(config.find_module("mod2::sub_mod::sub_mod_2"), Some(Debug));
1014        assert_eq!(config.find_module("mod3::sub_mod::sub_mod_2"), Some(Off));
1015    }
1016
1017    #[test]
1018    fn test_level_config_hierarchy_correct() {
1019        let config = LevelConfiguration::Minimal(
1020            vec![
1021                ("root", Trace),
1022                ("root::sub1", Debug),
1023                ("root::sub2", Info),
1024                // should work with all insertion orders
1025                ("root::sub2::sub2.3::sub2.4", Error),
1026                ("root::sub2::sub2.3", Warn),
1027                ("root::sub3", Off),
1028            ]
1029            .into_iter()
1030            .map(|(k, v)| (k.into(), v))
1031            .collect(),
1032        );
1033
1034        assert_eq!(config.find_module("root"), Some(Trace));
1035        assert_eq!(config.find_module("root::other_module"), Some(Trace));
1036
1037        // We want to ensure that it does pick up most specific level before trying
1038        // anything more general.
1039        assert_eq!(config.find_module("root::sub1"), Some(Debug));
1040        assert_eq!(config.find_module("root::sub1::other_module"), Some(Debug));
1041
1042        assert_eq!(config.find_module("root::sub2"), Some(Info));
1043        assert_eq!(config.find_module("root::sub2::other"), Some(Info));
1044
1045        assert_eq!(config.find_module("root::sub2::sub2.3"), Some(Warn));
1046        assert_eq!(
1047            config.find_module("root::sub2::sub2.3::sub2.4"),
1048            Some(Error)
1049        );
1050
1051        assert_eq!(config.find_module("root::sub3"), Some(Off));
1052        assert_eq!(
1053            config.find_module("root::sub3::any::children::of::sub3"),
1054            Some(Off)
1055        );
1056    }
1057
1058    #[test]
1059    fn test_level_config_similar_names_are_not_same() {
1060        let config = LevelConfiguration::Minimal(
1061            vec![("root", Trace), ("rootay", Info)]
1062                .into_iter()
1063                .map(|(k, v)| (k.into(), v))
1064                .collect(),
1065        );
1066
1067        assert_eq!(config.find_module("root"), Some(Trace));
1068        assert_eq!(config.find_module("root::sub"), Some(Trace));
1069        assert_eq!(config.find_module("rooty"), None);
1070        assert_eq!(config.find_module("rooty::sub"), None);
1071        assert_eq!(config.find_module("rootay"), Some(Info));
1072        assert_eq!(config.find_module("rootay::sub"), Some(Info));
1073    }
1074
1075    #[test]
1076    fn test_level_config_single_colon_is_not_double_colon() {
1077        let config = LevelConfiguration::Minimal(
1078            vec![
1079                ("root", Trace),
1080                ("root::su", Debug),
1081                ("root::su:b2", Info),
1082                ("root::sub2", Warn),
1083            ]
1084            .into_iter()
1085            .map(|(k, v)| (k.into(), v))
1086            .collect(),
1087        );
1088
1089        assert_eq!(config.find_module("root"), Some(Trace));
1090
1091        assert_eq!(config.find_module("root::su"), Some(Debug));
1092        assert_eq!(config.find_module("root::su::b2"), Some(Debug));
1093
1094        assert_eq!(config.find_module("root::su:b2"), Some(Info));
1095        assert_eq!(config.find_module("root::su:b2::b3"), Some(Info));
1096
1097        assert_eq!(config.find_module("root::sub2"), Some(Warn));
1098        assert_eq!(config.find_module("root::sub2::b3"), Some(Warn));
1099    }
1100
1101    #[test]
1102    fn test_level_config_all_chars() {
1103        let config = LevelConfiguration::Minimal(
1104            vec![("♲", Trace), ("☸", Debug), ("♲::☸", Info), ("♲::\t", Debug)]
1105                .into_iter()
1106                .map(|(k, v)| (k.into(), v))
1107                .collect(),
1108        );
1109
1110        assert_eq!(config.find_module("♲"), Some(Trace));
1111        assert_eq!(config.find_module("♲::other"), Some(Trace));
1112
1113        assert_eq!(config.find_module("☸"), Some(Debug));
1114        assert_eq!(config.find_module("☸::any"), Some(Debug));
1115
1116        assert_eq!(config.find_module("♲::☸"), Some(Info));
1117        assert_eq!(config.find_module("♲☸"), None);
1118
1119        assert_eq!(config.find_module("♲::\t"), Some(Debug));
1120        assert_eq!(config.find_module("♲::\t::\n\n::\t"), Some(Debug));
1121        assert_eq!(config.find_module("♲::\t\t"), Some(Trace));
1122    }
1123}