spdlog/formatter/pattern_formatter/
runtime.rs

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