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}