Skip to main content

spdlog/formatter/pattern_formatter/
runtime.rs

1use spdlog_internal::pattern_parser::{
2    error::TemplateError,
3    parse::{Template, TemplateToken},
4    BuiltInFormatter, Error as PatternParserError, PatternKind as GenericPatternKind,
5    PatternRegistry as GenericPatternRegistry, Result as PatternParserResult,
6};
7
8use super::{__pattern as pattern, Pattern, PatternContext};
9use crate::{
10    error::{BuildPatternError, Error},
11    Record, Result, StringBuf,
12};
13
14type Patterns = Vec<Box<dyn Pattern>>;
15type PatternCreator = Box<dyn Fn() -> Box<dyn Pattern>>;
16type PatternRegistry = GenericPatternRegistry<PatternCreator>;
17type PatternKind = GenericPatternKind<PatternCreator>;
18
19/// Builds a pattern from a template string at runtime.
20///
21/// It accepts inputs in the form:
22///
23/// ```ignore
24/// // This is not exactly a valid declarative macro, just for intuition.
25/// macro_rules! runtime_pattern {
26///     ( $template:expr $(,)? ) => {};
27///     ( $template:expr, $( {$$custom:ident} => $ctor:expr ),+ $(,)? ) => {};
28/// }
29/// ```
30///
31/// The only difference between `runtime_pattern!` macro and [`pattern!`] macro
32/// is that [`pattern!`] macro only accepts a string literal as the pattern
33/// template, while `runtime_pattern!` macro accepts an expression that can be
34/// evaluated to the pattern template string at runtime.
35///
36/// The returen type of `runtime_pattern!` macro is
37/// `Result<RuntimePattern, spdlog::Error>`. An error will be returned when
38/// parsing of the template string fails. If any of the custom patterns given
39/// are invalid, a compilation error will be triggered.
40///
41/// For the input formats and more usages, please refer to [`pattern!`] macro.
42///
43/// # Example
44///
45/// ```
46/// use spdlog::formatter::{runtime_pattern, PatternFormatter};
47///
48/// # type MyPattern = spdlog::formatter::__pattern::Level;
49/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
50/// let template = String::from("[{level}] {payload} - {$mypat}{eol}");
51/// let pat = runtime_pattern!(&template, {$mypat} => MyPattern::default)?;
52/// let formatter = PatternFormatter::new(pat);
53/// # Ok(()) }
54/// ```
55///
56/// [`pattern!`]: crate::formatter::pattern
57pub use spdlog_macros::runtime_pattern;
58
59#[rustfmt::skip] // rustfmt currently breaks some empty lines if `#[doc = include_str!("xxx")]` exists
60/// Runtime pattern built via [`runtime_pattern!`] macro.
61///
62/// ## Basic Usage
63/// 
64/// ```
65/// # use spdlog::formatter::{runtime_pattern, PatternFormatter};
66/// use spdlog::info;
67///
68/// #
69#[doc = include_str!(concat!(env!("OUT_DIR"), "/test_utils/common_for_doc_test.rs"))]
70/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
71/// let formatter = PatternFormatter::new(runtime_pattern!("[{level}] {payload}{eol}")?);
72/// # let (doctest, sink) = test_utils::echo_logger_from_formatter(formatter, None);
73///
74/// info!(logger: doctest, "Interesting log message");
75/// # assert_eq!(
76/// #     sink.clone_string().replace("\r", ""),
77/// /* Output */ "[info] Interesting log message\n"
78/// # );
79/// # Ok(()) }
80/// ```
81/// 
82/// ## With Custom Patterns
83/// 
84/// ```
85/// use std::fmt::Write;
86///
87/// use spdlog::{
88///     formatter::{pattern, Pattern, PatternContext, PatternFormatter, runtime_pattern, RuntimePattern},
89///     Record, StringBuf, info
90/// };
91/// 
92/// #[derive(Default, Clone)]
93/// struct MyPattern;
94/// 
95/// impl Pattern for MyPattern {
96///    fn format(&self, record: &Record, dest: &mut StringBuf, _: &mut PatternContext) -> spdlog::Result<()> {
97///        write!(dest, "My own pattern").map_err(spdlog::Error::FormatRecord)
98///    }
99/// }
100///
101#[doc = include_str!(concat!(env!("OUT_DIR"), "/test_utils/common_for_doc_test.rs"))]
102/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
103/// let template = "[{level}] {payload} - {$mypat1} {$mypat2}{eol}";
104/// # // TODO: Directly pass the closure to runtime_pattern! macro
105/// fn pat() -> impl Pattern { pattern!("[{level_short}-{level}]") }
106/// let formatter = PatternFormatter::new(
107///     runtime_pattern!(
108///         template,
109///         {$mypat1} => MyPattern::default,
110///         {$mypat2} => pat
111///     )?
112/// );
113/// # let (doctest, sink) = test_utils::echo_logger_from_formatter(formatter, None);
114///
115/// info!(logger: doctest, "Interesting log message");
116/// # assert_eq!(
117/// #     sink.clone_string().replace("\r", ""),
118/// /* Output */ "[info] Interesting log message - My own pattern [I-info]\n"
119/// # );
120/// # Ok(()) }
121/// ```
122/// 
123/// [`pattern!`]: crate::formatter::pattern
124#[derive(Clone)]
125pub struct RuntimePattern(Patterns);
126
127impl RuntimePattern {
128    // Private function, do not use in your code directly.
129    #[doc(hidden)]
130    pub fn __with_custom_patterns(template: &str, registry: PatternRegistry) -> Result<Self> {
131        Template::parse(template)
132            .and_then(|template| {
133                Synthesiser::new(registry)
134                    .synthesize(template)
135                    .map(RuntimePattern)
136            })
137            .map_err(|err| Error::BuildPattern(BuildPatternError(err)))
138    }
139}
140
141impl Pattern for RuntimePattern {
142    fn format(
143        &self,
144        record: &Record,
145        dest: &mut StringBuf,
146        ctx: &mut PatternContext,
147    ) -> Result<()> {
148        for pattern in &self.0 {
149            pattern.format(record, dest, ctx)?;
150        }
151        Ok(())
152    }
153}
154
155struct Synthesiser {
156    registry: PatternRegistry,
157}
158
159impl Synthesiser {
160    fn new(registry: PatternRegistry) -> Self {
161        Self { registry }
162    }
163
164    fn synthesize(&self, template: Template) -> PatternParserResult<Patterns> {
165        self.build_patterns(template, false)
166    }
167
168    fn build_patterns(
169        &self,
170        template: Template,
171        mut style_range_seen: bool,
172    ) -> PatternParserResult<Patterns> {
173        let mut patterns = Patterns::new();
174
175        for token in template.tokens {
176            let pattern = match token {
177                TemplateToken::Literal(t) => Box::new(t.literal),
178                TemplateToken::Formatter(t) => {
179                    let pattern = self.registry.find(t.has_custom_prefix, t.placeholder)?;
180                    match pattern {
181                        PatternKind::BuiltIn(builtin) => build_builtin_pattern(builtin),
182                        PatternKind::Custom { factory, .. } => factory(),
183                    }
184                }
185                TemplateToken::StyleRange(style_range) => {
186                    if style_range_seen {
187                        return Err(PatternParserError::Template(
188                            TemplateError::MultipleStyleRange,
189                        ));
190                    }
191                    style_range_seen = true;
192                    Box::new(pattern::StyleRange::new(
193                        self.build_patterns(style_range.body, true)?,
194                    ))
195                }
196            };
197            patterns.push(pattern);
198        }
199
200        Ok(patterns)
201    }
202}
203
204fn build_builtin_pattern(builtin: &BuiltInFormatter) -> Box<dyn Pattern> {
205    macro_rules! match_builtin {
206        (  $($name:ident),+ $(,)? ) => {
207            match builtin {
208                $(BuiltInFormatter::$name => Box::<pattern::$name>::default()),+
209            }
210        };
211    }
212
213    match_builtin!(
214        AbbrWeekdayName,
215        WeekdayName,
216        AbbrMonthName,
217        MonthName,
218        FullDateTime,
219        ShortYear,
220        Year,
221        ShortDate,
222        Date,
223        Month,
224        Day,
225        Hour,
226        Hour12,
227        Minute,
228        Second,
229        Millisecond,
230        Microsecond,
231        Nanosecond,
232        AmPm,
233        Time12,
234        ShortTime,
235        Time,
236        TzOffset,
237        UnixTimestamp,
238        Full,
239        Level,
240        ShortLevel,
241        Source,
242        SourceFilename,
243        SourceFile,
244        SourceLine,
245        SourceColumn,
246        SourceModulePath,
247        LoggerName,
248        Payload,
249        KV,
250        ProcessId,
251        ThreadId,
252        Eol
253    )
254}