flexi_logger/
flexi_logger.rs

1use crate::{
2    filter::LogLineFilter,
3    primary_writer::PrimaryWriter,
4    util::{eprint_err, eprint_msg, ErrorCode},
5    writers::LogWriter,
6    DeferredNow, LogSpecification,
7};
8
9#[cfg(feature = "textfilter")]
10use regex::Regex;
11use std::collections::HashMap;
12use std::sync::{Arc, RwLock};
13
14// Implements log::Log to plug into the log crate.
15//
16// Delegates the real logging to the configured PrimaryWriter and optionally to other writers.
17pub(crate) struct FlexiLogger {
18    log_specification: Arc<RwLock<LogSpecification>>,
19    primary_writer: Arc<PrimaryWriter>,
20    other_writers: Arc<HashMap<String, Box<dyn LogWriter>>>,
21    filter: Option<Box<dyn LogLineFilter + Send + Sync>>,
22}
23
24impl FlexiLogger {
25    pub fn new(
26        log_specification: Arc<RwLock<LogSpecification>>,
27        primary_writer: Arc<PrimaryWriter>,
28        other_writers: Arc<HashMap<String, Box<dyn LogWriter>>>,
29        filter: Option<Box<dyn LogLineFilter + Send + Sync>>,
30    ) -> Self {
31        Self {
32            log_specification,
33            primary_writer,
34            other_writers,
35            filter,
36        }
37    }
38
39    fn primary_enabled(&self, level: log::Level, module: &str) -> bool {
40        self.log_specification
41            .read()
42            .map_err(|e| eprint_err(ErrorCode::Poison, "rwlock on log spec is poisoned", &e))
43            .unwrap()
44            .enabled(level, module)
45    }
46}
47
48impl log::Log for FlexiLogger {
49    //  If other writers are configured and the metadata target addresses them correctly,
50    //      - we should determine if the metadata-level is digested by any of the writers
51    //        (including the primary writer)
52    //  else we fall back to default behavior:
53    //      Return true if
54    //      - target is filled with module path and level is accepted by log specification
55    //      - target is filled with crap and ???
56    fn enabled(&self, metadata: &log::Metadata) -> bool {
57        let target = metadata.target();
58        let level = metadata.level();
59
60        if !self.other_writers.is_empty() && target.starts_with('{') {
61            // at least one other writer is configured _and_ addressed
62            let targets: Vec<&str> = target[1..(target.len() - 1)].split(',').collect();
63            for t in targets {
64                if t != "_Default" {
65                    match self.other_writers.get(t) {
66                        None => {
67                            eprint_msg(ErrorCode::WriterSpec, &format!("bad writer spec: {t}"));
68                        }
69                        Some(writer) => {
70                            if level < writer.max_log_level() {
71                                return true;
72                            }
73                        }
74                    }
75                }
76            }
77        }
78
79        self.primary_enabled(level, target)
80    }
81
82    fn log(&self, record: &log::Record) {
83        let target = record.metadata().target();
84        let mut now = DeferredNow::new();
85        let special_target_is_used = target.starts_with('{');
86        if special_target_is_used {
87            let mut use_default = false;
88            let targets: Vec<&str> = target[1..(target.len() - 1)].split(',').collect();
89            for t in targets {
90                if t == "_Default" {
91                    use_default = true;
92                } else {
93                    match self.other_writers.get(t) {
94                        None => {
95                            eprint_msg(ErrorCode::WriterSpec, &format!("bad writer spec: {t}"));
96                        }
97                        Some(writer) => {
98                            writer.write(&mut now, record).unwrap_or_else(|e| {
99                                eprint_err(
100                                    ErrorCode::Write,
101                                    &format!("writing log line to custom writer \"{t}\" failed"),
102                                    &e,
103                                );
104                            });
105                        }
106                    }
107                }
108            }
109            if !use_default {
110                return;
111            }
112        }
113
114        let effective_target = if special_target_is_used {
115            record.module_path().unwrap_or_default()
116        } else {
117            target
118        };
119        if !self.primary_enabled(record.level(), effective_target) {
120            return;
121        }
122
123        #[cfg(feature = "textfilter")]
124        {
125            // closure that we need below
126            let check_text_filter = |text_filter: Option<&Regex>| {
127                text_filter.is_none_or(|filter| filter.is_match(&record.args().to_string()))
128            };
129
130            if !check_text_filter(
131                self.log_specification.read().as_ref().unwrap(/* expose this? */).text_filter(),
132            ) {
133                return;
134            }
135        }
136
137        if let Some(ref filter) = self.filter {
138            filter.write(&mut now, record, &(*self.primary_writer))
139        } else {
140            self.primary_writer.write(&mut now, record)
141        }
142        .unwrap_or_else(|e| {
143            eprint_err(ErrorCode::Write, "writing log line failed", &e);
144        });
145    }
146
147    fn flush(&self) {
148        self.primary_writer.flush().unwrap_or_else(|e| {
149            eprint_err(ErrorCode::Flush, "flushing primary writer failed", &e);
150        });
151        for writer in self.other_writers.values() {
152            writer.flush().unwrap_or_else(|e| {
153                eprint_err(ErrorCode::Flush, "flushing custom writer failed", &e);
154            });
155        }
156    }
157}