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}