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(
74/// # Box::new(formatter),
75/// # None
76/// # );
77///
78/// info!(logger: doctest, "Interesting log message");
79/// # assert_eq!(
80/// # sink.clone_string().replace("\r", ""),
81/// /* Output */ "[info] Interesting log message\n"
82/// # );
83/// # Ok(()) }
84/// ```
85///
86/// ## With Custom Patterns
87///
88/// ```
89/// use std::fmt::Write;
90///
91/// use spdlog::{
92/// formatter::{pattern, Pattern, PatternContext, PatternFormatter, runtime_pattern, RuntimePattern},
93/// Record, StringBuf, info
94/// };
95///
96/// #[derive(Default, Clone)]
97/// struct MyPattern;
98///
99/// impl Pattern for MyPattern {
100/// fn format(&self, record: &Record, dest: &mut StringBuf, _: &mut PatternContext) -> spdlog::Result<()> {
101/// write!(dest, "My own pattern").map_err(spdlog::Error::FormatRecord)
102/// }
103/// }
104///
105#[doc = include_str!(concat!(env!("OUT_DIR"), "/test_utils/common_for_doc_test.rs"))]
106/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
107/// let template = "[{level}] {payload} - {$mypat1} {$mypat2}{eol}";
108/// # // TODO: Directly pass the closure to runtime_pattern! macro
109/// fn pat() -> impl Pattern { pattern!("[{level_short}-{level}]") }
110/// let formatter = PatternFormatter::new(
111/// runtime_pattern!(
112/// template,
113/// {$mypat1} => MyPattern::default,
114/// {$mypat2} => pat
115/// )?
116/// );
117/// # let (doctest, sink) = test_utils::echo_logger_from_formatter(
118/// # Box::new(formatter),
119/// # None
120/// # );
121///
122/// info!(logger: doctest, "Interesting log message");
123/// # assert_eq!(
124/// # sink.clone_string().replace("\r", ""),
125/// /* Output */ "[info] Interesting log message - My own pattern [I-info]\n"
126/// # );
127/// # Ok(()) }
128/// ```
129///
130/// [`pattern!`]: crate::formatter::pattern
131#[derive(Clone)]
132pub struct RuntimePattern(Patterns);
133
134impl RuntimePattern {
135 // Private function, do not use in your code directly.
136 #[doc(hidden)]
137 pub fn __with_custom_patterns(template: &str, registry: PatternRegistry) -> Result<Self> {
138 Template::parse(template)
139 .and_then(|template| {
140 Synthesiser::new(registry)
141 .synthesize(template)
142 .map(RuntimePattern)
143 })
144 .map_err(|err| Error::BuildPattern(BuildPatternError(err)))
145 }
146}
147
148impl Pattern for RuntimePattern {
149 fn format(
150 &self,
151 record: &Record,
152 dest: &mut StringBuf,
153 ctx: &mut PatternContext,
154 ) -> Result<()> {
155 for pattern in &self.0 {
156 pattern.format(record, dest, ctx)?;
157 }
158 Ok(())
159 }
160}
161
162struct Synthesiser {
163 registry: PatternRegistry,
164}
165
166impl Synthesiser {
167 fn new(registry: PatternRegistry) -> Self {
168 Self { registry }
169 }
170
171 fn synthesize(&self, template: Template) -> PatternParserResult<Patterns> {
172 self.build_patterns(template, false)
173 }
174
175 fn build_patterns(
176 &self,
177 template: Template,
178 mut style_range_seen: bool,
179 ) -> PatternParserResult<Patterns> {
180 let mut patterns = Patterns::new();
181
182 for token in template.tokens {
183 let pattern = match token {
184 TemplateToken::Literal(t) => Box::new(t.literal),
185 TemplateToken::Formatter(t) => {
186 let pattern = self.registry.find(t.has_custom_prefix, t.placeholder)?;
187 match pattern {
188 PatternKind::BuiltIn(builtin) => build_builtin_pattern(builtin),
189 PatternKind::Custom { factory, .. } => factory(),
190 }
191 }
192 TemplateToken::StyleRange(style_range) => {
193 if style_range_seen {
194 return Err(PatternParserError::Template(
195 TemplateError::MultipleStyleRange,
196 ));
197 }
198 style_range_seen = true;
199 Box::new(pattern::StyleRange::new(
200 self.build_patterns(style_range.body, true)?,
201 ))
202 }
203 };
204 patterns.push(pattern);
205 }
206
207 Ok(patterns)
208 }
209}
210
211fn build_builtin_pattern(builtin: &BuiltInFormatter) -> Box<dyn Pattern> {
212 macro_rules! match_builtin {
213 ( $($name:ident),+ $(,)? ) => {
214 match builtin.inner() {
215 $(BuiltInFormatterInner::$name => Box::<pattern::$name>::default()),+
216 }
217 };
218 }
219
220 match_builtin!(
221 AbbrWeekdayName,
222 WeekdayName,
223 AbbrMonthName,
224 MonthName,
225 FullDateTime,
226 ShortYear,
227 Year,
228 ShortDate,
229 Date,
230 Month,
231 Day,
232 Hour,
233 Hour12,
234 Minute,
235 Second,
236 Millisecond,
237 Microsecond,
238 Nanosecond,
239 AmPm,
240 Time12,
241 ShortTime,
242 Time,
243 TzOffset,
244 UnixTimestamp,
245 Full,
246 Level,
247 ShortLevel,
248 Source,
249 SourceFilename,
250 SourceFile,
251 SourceLine,
252 SourceColumn,
253 SourceModulePath,
254 LoggerName,
255 Payload,
256 ProcessId,
257 ThreadId,
258 Eol
259 )
260}