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}