Skip to main content

tari_log4rs/
lib.rs

1//! log4rs is a highly configurable logging framework modeled after Java's
2//! Logback and log4j libraries.
3//!
4//! # Architecture
5//!
6//! The basic units of configuration are *appenders*, *encoders*, *filters*, and
7//! *loggers*.
8//!
9//! ## Appenders
10//!
11//! An appender takes a log record and logs it somewhere, for example, to a
12//! file, the console, or the syslog.
13//!
14//! Implementations:
15//!   - [console](append/console/struct.ConsoleAppenderDeserializer.html#configuration): requires the `console_appender` feature.
16//!   - [file](append/file/struct.FileAppenderDeserializer.html#configuration): requires the `file_appender` feature.
17//!   - [rolling_file](append/rolling_file/struct.RollingFileAppenderDeserializer.html#configuration): requires the `rolling_file_appender` feature and can be configured with the `compound_policy`.
18//!     - [compound](append/rolling_file/policy/compound/struct.CompoundPolicyDeserializer.html#configuration): requires the `compound_policy` feature
19//!       - Rollers
20//!         - [delete](append/rolling_file/policy/compound/roll/delete/struct.DeleteRollerDeserializer.html#configuration): requires the `delete_roller` feature
21//!         - [fixed_window](append/rolling_file/policy/compound/roll/fixed_window/struct.FixedWindowRollerDeserializer.html#configuration): requires the `fixed_window_roller` feature
22//!       - Triggers
23//!         - [size](append/rolling_file/policy/compound/trigger/size/struct.SizeTriggerDeserializer.html#configuration): requires the `size_trigger` feature
24//!
25//! ## Encoders
26//!
27//! An encoder is responsible for taking a log record, transforming it into the
28//! appropriate output format, and writing it out. An appender will normally
29//! use an encoder internally.
30//!
31//! Implementations:
32//!   - [pattern](encode/pattern/struct.PatternEncoderDeserializer.html#configuration): requires the `pattern_encoder` feature
33//!   - [json](encode/json/struct.JsonEncoderDeserializer.html#configuration): requires the `json_encoder` feature
34//!
35//! ## Filters
36//!
37//! Filters are associated with appenders and, like the name would suggest,
38//! filter log events coming into that appender.
39//!
40//! Implementations:
41//!   - [threshold](filter/threshold/struct.ThresholdFilterDeserializer.html#configuration): requires the `threshold_filter` feature
42//!
43//! ## Loggers
44//!
45//! A log event is targeted at a specific logger, which are identified by
46//! string names. The logging macros built in to the `log` crate set the logger
47//! of a log event to the one identified by the module containing the
48//! invocation location.
49//!
50//! Loggers form a hierarchy: logger names are divided into components by "::".
51//! One logger is the ancestor of another if the first logger's component list
52//! is a prefix of the second logger's component list.
53//!
54//! Loggers are associated with a maximum log level. Log events for that logger
55//! with a level above the maximum will be ignored. The maximum log level for
56//! any logger can be configured manually; if it is not, the level will be
57//! inherited from the logger's parent.
58//!
59//! Loggers are also associated with a set of appenders. Appenders can be
60//! associated directly with a logger. In addition, the appenders of the
61//! logger's parent will be associated with the logger unless the logger has
62//! its *additive* set to `false`. Log events sent to the logger that are not
63//! filtered out by the logger's maximum log level will be sent to all
64//! associated appenders.
65//!
66//! The "root" logger is the ancestor of all other loggers. Since it has no
67//! ancestors, its additivity cannot be configured.
68//!
69//! # Configuration
70//!
71//! log4rs can be configured programmatically by using the builders in the
72//! `config` module to construct a log4rs `Config` object, which can be passed
73//! to the `init_config` function.
74//!
75//! The more common configuration method, however, is via a separate config
76//! file. The `init_file` function takes the path to a config file as
77//! well as a `Deserializers` object which is responsible for instantiating the
78//! various objects specified by the config file. The `file` module
79//! documentation covers the exact configuration syntax, but an example in the
80//! YAML format is provided below.
81//!
82//! log4rs makes heavy use of Cargo features to enable consumers to pick the
83//! functionality they wish to use. File-based configuration requires the `file`
84//! feature, and each file format requires its own feature as well. In addition,
85//! each component has its own feature. For example, YAML support requires the
86//! `yaml_format` feature and the console appender requires the
87//! `console_appender` feature.
88//!
89//! By default, the `all_components`, `gzip`, `file`, and `yaml_format` features
90//! are enabled.
91//!
92//! As a convenience, the `all_components` feature activates all logger components.
93//!
94//! # Examples
95//!
96//! ## Configuration via a YAML file
97//!
98//! ```yaml
99//! # Scan this file for changes every 30 seconds
100//! refresh_rate: 30 seconds
101//!
102//! appenders:
103//!   # An appender named "stdout" that writes to stdout
104//!   stdout:
105//!     kind: console
106//!
107//!   # An appender named "requests" that writes to a file with a custom pattern encoder
108//!   requests:
109//!     kind: file
110//!     path: "log/requests.log"
111//!     encoder:
112//!       pattern: "{d} - {m}{n}"
113//!
114//! # Set the default logging level to "warn" and attach the "stdout" appender to the root
115//! root:
116//!   level: warn
117//!   appenders:
118//!     - stdout
119//!
120//! loggers:
121//!   # Raise the maximum log level for events sent to the "app::backend::db" logger to "info"
122//!   app::backend::db:
123//!     level: info
124//!
125//!   # Route log events sent to the "app::requests" logger to the "requests" appender,
126//!   # and *not* the normal appenders installed at the root
127//!   app::requests:
128//!     level: info
129//!     appenders:
130//!       - requests
131//!     additive: false
132//! ```
133//!
134//! Add the following in your application initialization.
135//!
136//! ```no_run
137//! # #[cfg(feature = "config_parsing")]
138//! # fn f() {
139//! log4rs::init_file("log4rs.yml", Default::default()).unwrap();
140//! # }
141//! ```
142//!
143//! ## Programmatically constructing a configuration:
144//!
145//! ```no_run
146//! # #[cfg(all(feature = "console_appender",
147//! #           feature = "file_appender",
148//! #           feature = "pattern_encoder"))]
149//! # fn f() {
150//! use log::LevelFilter;
151//! use log4rs::append::console::ConsoleAppender;
152//! use log4rs::append::file::FileAppender;
153//! use log4rs::encode::pattern::PatternEncoder;
154//! use log4rs::config::{Appender, Config, Logger, Root};
155//!
156//! fn main() {
157//!     let stdout = ConsoleAppender::builder().build();
158//!
159//!     let requests = FileAppender::builder()
160//!         .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
161//!         .build("log/requests.log")
162//!         .unwrap();
163//!
164//!     let config = Config::builder()
165//!         .appender(Appender::builder().build("stdout", Box::new(stdout)))
166//!         .appender(Appender::builder().build("requests", Box::new(requests)))
167//!         .logger(Logger::builder().build("app::backend::db", LevelFilter::Info))
168//!         .logger(Logger::builder()
169//!             .appender("requests")
170//!             .additive(false)
171//!             .build("app::requests", LevelFilter::Info))
172//!         .build(Root::builder().appender("stdout").build(LevelFilter::Warn))
173//!         .unwrap();
174//!
175//!     let handle = log4rs::init_config(config).unwrap();
176//!
177//!     // use handle to change logger configuration at runtime
178//! }
179//! # }
180//! # fn main() {}
181//! ```
182//!
183//! For more examples see the [examples](https://github.com/estk/log4rs/tree/master/examples).
184//!
185
186#![allow(where_clauses_object_safety, clippy::manual_non_exhaustive)]
187#![warn(missing_docs)]
188
189use std::{
190    cmp, collections::HashMap, fmt, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc,
191};
192
193use arc_swap::ArcSwap;
194use fnv::FnvHasher;
195use log::{Level, LevelFilter, Metadata, Record};
196
197pub mod append;
198pub mod config;
199pub mod encode;
200pub mod filter;
201#[cfg(feature = "console_writer")]
202mod priv_io;
203
204pub use config::{init_config, Config};
205
206#[cfg(feature = "config_parsing")]
207pub use config::{init_file, init_raw_config};
208
209use self::{append::Append, filter::Filter};
210
211type FnvHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FnvHasher>>;
212
213#[derive(Debug)]
214struct ConfiguredLogger {
215    level: LevelFilter,
216    appenders: Vec<usize>,
217    children: FnvHashMap<String, ConfiguredLogger>,
218}
219
220impl ConfiguredLogger {
221    fn add(&mut self, path: &str, mut appenders: Vec<usize>, additive: bool, level: LevelFilter) {
222        let (part, rest) = match path.find("::") {
223            Some(idx) => (&path[..idx], &path[idx + 2..]),
224            None => (path, ""),
225        };
226
227        if let Some(child) = self.children.get_mut(part) {
228            child.add(rest, appenders, additive, level);
229            return;
230        }
231
232        let child = if rest.is_empty() {
233            if additive {
234                appenders.extend(self.appenders.iter().cloned());
235            }
236
237            ConfiguredLogger {
238                level,
239                appenders,
240                children: FnvHashMap::default(),
241            }
242        } else {
243            let mut child = ConfiguredLogger {
244                level: self.level,
245                appenders: self.appenders.clone(),
246                children: FnvHashMap::default(),
247            };
248            child.add(rest, appenders, additive, level);
249            child
250        };
251
252        self.children.insert(part.to_owned(), child);
253    }
254
255    fn max_log_level(&self) -> LevelFilter {
256        let mut max = self.level;
257        for child in self.children.values() {
258            max = cmp::max(max, child.max_log_level());
259        }
260        max
261    }
262
263    fn find(&self, path: &str) -> &ConfiguredLogger {
264        let mut node = self;
265
266        for part in path.split("::") {
267            match node.children.get(part) {
268                Some(child) => node = child,
269                None => break,
270            }
271        }
272
273        node
274    }
275
276    fn enabled(&self, level: Level) -> bool {
277        self.level >= level
278    }
279
280    fn log(&self, record: &log::Record, appenders: &[Appender]) -> Result<(), Vec<anyhow::Error>> {
281        let mut errors = vec![];
282        if self.enabled(record.level()) {
283            for &idx in &self.appenders {
284                if let Err(err) = appenders[idx].append(record) {
285                    errors.push(err);
286                }
287            }
288        }
289
290        if errors.is_empty() {
291            Ok(())
292        } else {
293            Err(errors)
294        }
295    }
296}
297
298#[derive(Debug)]
299struct Appender {
300    appender: Box<dyn Append>,
301    filters: Vec<Box<dyn Filter>>,
302}
303
304impl Appender {
305    fn append(&self, record: &Record) -> anyhow::Result<()> {
306        for filter in &self.filters {
307            match filter.filter(record) {
308                filter::Response::Accept => break,
309                filter::Response::Neutral => {}
310                filter::Response::Reject => return Ok(()),
311            }
312        }
313
314        self.appender.append(record)
315    }
316
317    fn flush(&self) {
318        self.appender.flush();
319    }
320}
321
322struct SharedLogger {
323    root: ConfiguredLogger,
324    appenders: Vec<Appender>,
325    err_handler: Box<dyn Send + Sync + Fn(&anyhow::Error)>,
326}
327
328impl fmt::Debug for SharedLogger {
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        f.debug_struct("SharedLogger")
331            .field("root", &self.root)
332            .field("appenders", &self.appenders)
333            .finish()
334    }
335}
336
337impl SharedLogger {
338    fn new(config: config::Config) -> SharedLogger {
339        Self::new_with_err_handler(
340            config,
341            Box::new(|e: &anyhow::Error| {
342                let _ = writeln!(io::stderr(), "log4rs: {}", e);
343            }),
344        )
345    }
346    fn new_with_err_handler(
347        config: config::Config,
348        err_handler: Box<dyn Send + Sync + Fn(&anyhow::Error)>,
349    ) -> SharedLogger {
350        let (appenders, root, mut loggers) = config.unpack();
351
352        let root = {
353            let appender_map = appenders
354                .iter()
355                .enumerate()
356                .map(|(i, appender)| (appender.name(), i))
357                .collect::<HashMap<_, _>>();
358
359            let mut root = ConfiguredLogger {
360                level: root.level(),
361                appenders: root
362                    .appenders()
363                    .iter()
364                    .map(|appender| appender_map[&**appender])
365                    .collect(),
366                children: FnvHashMap::default(),
367            };
368
369            // sort loggers by name length to ensure that we initialize them top to bottom
370            loggers.sort_by_key(|l| l.name().len());
371            for logger in loggers {
372                let appenders = logger
373                    .appenders()
374                    .iter()
375                    .map(|appender| appender_map[&**appender])
376                    .collect();
377                root.add(logger.name(), appenders, logger.additive(), logger.level());
378            }
379
380            root
381        };
382
383        let appenders = appenders
384            .into_iter()
385            .map(|appender| {
386                let (_, appender, filters) = appender.unpack();
387                Appender { appender, filters }
388            })
389            .collect();
390
391        SharedLogger {
392            root,
393            appenders,
394            err_handler,
395        }
396    }
397}
398
399/// The fully configured log4rs Logger which is appropriate
400/// to use with the `log::set_boxed_logger` function.
401#[derive(Debug)]
402pub struct Logger(Arc<ArcSwap<SharedLogger>>);
403
404impl Logger {
405    /// Create a new `Logger` given a configuration.
406    pub fn new(config: config::Config) -> Logger {
407        Logger(Arc::new(ArcSwap::new(Arc::new(SharedLogger::new(config)))))
408    }
409    /// Create a new `Logger` given a configuration and err handler.
410    pub fn new_with_err_handler(
411        config: config::Config,
412        err_handler: Box<dyn Send + Sync + Fn(&anyhow::Error)>,
413    ) -> Logger {
414        Logger(Arc::new(ArcSwap::new(Arc::new(
415            SharedLogger::new_with_err_handler(config, err_handler),
416        ))))
417    }
418
419    /// Set the max log level above which everything will be filtered.
420    pub fn max_log_level(&self) -> LevelFilter {
421        self.0.load().root.max_log_level()
422    }
423}
424
425impl log::Log for Logger {
426    fn enabled(&self, metadata: &Metadata) -> bool {
427        self.0
428            .load()
429            .root
430            .find(metadata.target())
431            .enabled(metadata.level())
432    }
433
434    fn log(&self, record: &log::Record) {
435        let shared = self.0.load();
436        if let Err(errs) = shared
437            .root
438            .find(record.target())
439            .log(record, &shared.appenders)
440        {
441            for e in errs {
442                (shared.err_handler)(&e)
443            }
444        }
445    }
446
447    fn flush(&self) {
448        for appender in &self.0.load().appenders {
449            appender.flush();
450        }
451    }
452}
453
454pub(crate) fn handle_error(e: &anyhow::Error) {
455    let _ = writeln!(io::stderr(), "log4rs: {}", e);
456}
457
458/// A handle to the active logger.
459#[derive(Clone, Debug)]
460pub struct Handle {
461    shared: Arc<ArcSwap<SharedLogger>>,
462}
463
464impl Handle {
465    /// Sets the logging configuration.
466    pub fn set_config(&self, config: Config) {
467        let shared = SharedLogger::new(config);
468        log::set_max_level(shared.root.max_log_level());
469        self.shared.store(Arc::new(shared));
470    }
471}
472
473trait ErrorInternals {
474    fn new(message: String) -> Self;
475}
476
477#[cfg(test)]
478mod test {
479    use log::{Level, LevelFilter, Log};
480
481    use super::*;
482
483    #[test]
484    #[cfg(all(feature = "config_parsing", feature = "json_format"))]
485    fn init_from_raw_config() {
486        let dir = tempfile::tempdir().unwrap();
487        let path = dir.path().join("append.log");
488
489        let cfg = serde_json::json!({
490            "refresh_rate": "60 seconds",
491            "root" : {
492                "appenders": ["baz"],
493                "level": "info",
494            },
495            "appenders": {
496                "baz": {
497                    "kind": "file",
498                    "path": path,
499                    "encoder": {
500                        "pattern": "{m}"
501                    }
502                }
503            },
504        });
505        let config = serde_json::from_str::<config::RawConfig>(&cfg.to_string()).unwrap();
506        if let Err(e) = init_raw_config(config) {
507            panic!("{}", e);
508        }
509        assert!(path.exists());
510        log::info!("init_from_raw_config");
511
512        let mut contents = String::new();
513        std::fs::File::open(&path)
514            .unwrap()
515            .read_to_string(&mut contents)
516            .unwrap();
517        assert_eq!(contents, "init_from_raw_config");
518    }
519
520    #[test]
521    fn enabled() {
522        let root = config::Root::builder().build(LevelFilter::Debug);
523        let mut config = config::Config::builder();
524        let logger = config::Logger::builder().build("foo::bar", LevelFilter::Trace);
525        config = config.logger(logger);
526        let logger = config::Logger::builder().build("foo::bar::baz", LevelFilter::Off);
527        config = config.logger(logger);
528        let logger = config::Logger::builder().build("foo::baz::buz", LevelFilter::Error);
529        config = config.logger(logger);
530        let config = config.build(root).unwrap();
531
532        let logger = super::Logger::new(config);
533
534        assert!(logger.enabled(&Metadata::builder().level(Level::Warn).target("bar").build()));
535        assert!(!logger.enabled(
536            &Metadata::builder()
537                .level(Level::Trace)
538                .target("bar")
539                .build()
540        ));
541        assert!(logger.enabled(
542            &Metadata::builder()
543                .level(Level::Debug)
544                .target("foo")
545                .build()
546        ));
547        assert!(logger.enabled(
548            &Metadata::builder()
549                .level(Level::Trace)
550                .target("foo::bar")
551                .build()
552        ));
553        assert!(!logger.enabled(
554            &Metadata::builder()
555                .level(Level::Error)
556                .target("foo::bar::baz")
557                .build()
558        ));
559        assert!(logger.enabled(
560            &Metadata::builder()
561                .level(Level::Debug)
562                .target("foo::bar::bazbuz")
563                .build()
564        ));
565        assert!(!logger.enabled(
566            &Metadata::builder()
567                .level(Level::Error)
568                .target("foo::bar::baz::buz")
569                .build()
570        ));
571        assert!(!logger.enabled(
572            &Metadata::builder()
573                .level(Level::Warn)
574                .target("foo::baz::buz")
575                .build()
576        ));
577        assert!(!logger.enabled(
578            &Metadata::builder()
579                .level(Level::Warn)
580                .target("foo::baz::buz::bar")
581                .build()
582        ));
583        assert!(logger.enabled(
584            &Metadata::builder()
585                .level(Level::Error)
586                .target("foo::baz::buz::bar")
587                .build()
588        ));
589    }
590}