log4rs_routing_appender/route/pattern/
mod.rs

1//! A router which constructs appenders from a template configuration.
2//!
3//! Strings in the configuration template may contain substitution directives. The format is similar
4//! to that of the log4rs pattern encoder, except that it is prefixed with a `$` to avoid conflicts
5//! with patterns in the templated configuration itself. Format specifications are not supported.
6//!
7//! Only one formatter is currently supported:
8//!
9//! * `mdc` - An entry from the [MDC][MDC]. The first argument is required, and specifies the key to
10//!     look up. If the key is not present, an error is raised. A second, optional argument allows
11//!     a replacement string to be used if the key is not present.
12//!
13//! # Examples
14//!
15//! Assume the MDC looks like `{user_id: sfackler}`.
16//!
17//! ```yaml
18//! kind: file
19//! path: "logs/${mdc(user_id)}/${mdc(job_id)}.log"
20//! ```
21//!
22//! will fail to parse, since there is no MDC entry for `job_id`. If we add a default value, like
23//!
24//! ```yaml
25//! kind: file
26//! path: "logs/${mdc(user_id)}/${mdc(job_id)(no_job)}.log"
27//! ```
28//!
29//! it will then parse to
30//!
31//! ```yaml
32//! kind: file
33//! path: "logs/sfackler/no_job.log"
34//! ```
35//!
36//! [MDC]: https://crates.io/crates/log-mdc
37use log4rs::file::{Deserialize, Deserializers};
38use log::Record;
39use serde::de;
40use serde_value::Value;
41use std::collections::BTreeMap;
42use std::error::Error;
43use std::fmt;
44
45use route::{Appender, Cache, Entry, Route};
46use route::pattern::template::Template;
47
48mod parser;
49mod template;
50
51/// Configuration for the `PatternRouter`.
52#[derive(Deserialize)]
53#[serde(deny_unknown_fields)]
54pub struct PatternRouterConfig {
55    pattern: AppenderConfig,
56}
57
58/// A router which expands an appender configuration template.
59pub struct PatternRouter {
60    deserializers: Deserializers,
61    kind: String,
62    config: Template,
63}
64
65impl fmt::Debug for PatternRouter {
66    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
67        fmt.debug_struct("PatternRouter").finish()
68    }
69}
70
71impl Route for PatternRouter {
72    fn route(&self, _: &Record, cache: &mut Cache) -> Result<Appender, Box<Error + Sync + Send>> {
73        match cache.entry(self.config.key()) {
74            Entry::Occupied(e) => Ok(e.into_value()),
75            Entry::Vacant(e) => {
76                let appender = self.deserializers
77                    .deserialize(&self.kind, self.config.expand()?)?;
78                Ok(e.insert(appender))
79            }
80        }
81    }
82}
83
84/// A deserializer for the `PatternRouter`.
85///
86/// # Configuration
87///
88/// ```yaml
89/// kind: pattern
90///
91/// # The configuration template to expand. Required.
92/// pattern:
93///   kind: file
94///   path: "logs/${mdc(user_id)}/${mdc(job_id)(no_job)}.log"
95/// ```
96pub struct PatternRouterDeserializer;
97
98impl Deserialize for PatternRouterDeserializer {
99    type Trait = Route;
100    type Config = PatternRouterConfig;
101
102    fn deserialize(
103        &self,
104        config: PatternRouterConfig,
105        deserializers: &Deserializers,
106    ) -> Result<Box<Route>, Box<Error + Sync + Send>> {
107        Ok(Box::new(PatternRouter {
108            deserializers: deserializers.clone(),
109            kind: config.pattern.kind,
110            config: Template::new(&config.pattern.config)?,
111        }))
112    }
113}
114
115struct AppenderConfig {
116    kind: String,
117    config: Value,
118}
119
120impl<'de> de::Deserialize<'de> for AppenderConfig {
121    fn deserialize<D>(d: D) -> Result<AppenderConfig, D::Error>
122    where
123        D: de::Deserializer<'de>,
124    {
125        let mut map = BTreeMap::<Value, Value>::deserialize(d)?;
126
127        let kind = match map.remove(&Value::String("kind".to_owned())) {
128            Some(kind) => kind.deserialize_into().map_err(|e| e.to_error())?,
129            None => return Err(de::Error::missing_field("kind")),
130        };
131
132        Ok(AppenderConfig {
133            kind: kind,
134            config: Value::Map(map),
135        })
136    }
137}