Skip to content

Commit 8bc177d

Browse files
committed
Introduce PartialFormatter to allow selection of formatting parts
1 parent 3353acc commit 8bc177d

File tree

2 files changed

+360
-0
lines changed

2 files changed

+360
-0
lines changed

spdlog/src/formatter/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ mod journald_formatter;
6565
#[cfg(feature = "serde_json")]
6666
mod json_formatter;
6767
mod local_time_cacher;
68+
mod partial_formatter;
6869
mod pattern_formatter;
6970
mod unreachable_formatter;
7071

@@ -85,6 +86,7 @@ pub(crate) use journald_formatter::*;
8586
#[cfg(feature = "serde_json")]
8687
pub use json_formatter::*;
8788
pub(crate) use local_time_cacher::*;
89+
pub use partial_formatter::*;
8890
pub use pattern_formatter::*;
8991
pub(crate) use unreachable_formatter::*;
9092

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
use std::fmt::{self, Write};
2+
3+
use crate::{
4+
formatter::{fmt_with_time, Formatter, FormatterContext, TimeDate},
5+
Error, Record, StringBuf, __EOL,
6+
};
7+
8+
#[derive(Clone)]
9+
pub struct PartialFormatter {
10+
options: FormattingOptions,
11+
}
12+
13+
impl PartialFormatter {
14+
#[must_use]
15+
pub fn builder() -> PartialFormatterBuilder {
16+
PartialFormatterBuilder(FormattingOptions {
17+
time: true,
18+
logger_name: true,
19+
level: true,
20+
source_location: true,
21+
kv: true,
22+
eol: true,
23+
})
24+
}
25+
26+
fn format_impl(
27+
&self,
28+
record: &Record,
29+
dest: &mut StringBuf,
30+
ctx: &mut FormatterContext,
31+
) -> Result<(), fmt::Error> {
32+
#[cfg(not(feature = "flexible-string"))]
33+
dest.reserve(crate::string_buf::RESERVE_SIZE);
34+
35+
let mut spacer = AutoSpacer::new();
36+
37+
spacer.write_if(self.options.time, dest, |dest| {
38+
fmt_with_time(
39+
ctx,
40+
record,
41+
|mut time: TimeDate| -> Result<(), fmt::Error> {
42+
dest.write_str("[")?;
43+
dest.write_str(time.full_second_str())?;
44+
dest.write_str(".")?;
45+
write!(dest, "{:03}", time.millisecond())?;
46+
dest.write_str("]")?;
47+
Ok(())
48+
},
49+
)
50+
})?;
51+
spacer.write_if_opt(
52+
self.options.logger_name,
53+
record.logger_name(),
54+
dest,
55+
|dest, logger_name| {
56+
dest.write_str("[")?;
57+
dest.write_str(logger_name)?;
58+
dest.write_str("]")
59+
},
60+
)?;
61+
let mut style_range = None;
62+
spacer.write_if(self.options.level, dest, |dest| {
63+
dest.write_str("[")?;
64+
let style_range_begin = dest.len();
65+
dest.write_str(record.level().as_str())?;
66+
let style_range_end = dest.len();
67+
dest.write_str("]")?;
68+
style_range = Some(style_range_begin..style_range_end);
69+
Ok(())
70+
})?;
71+
spacer.write_if_opt(
72+
self.options.source_location,
73+
record.source_location(),
74+
dest,
75+
|dest, srcloc| {
76+
dest.write_str("[")?;
77+
dest.write_str(srcloc.module_path())?;
78+
dest.write_str(", ")?;
79+
dest.write_str(srcloc.file())?;
80+
dest.write_str(":")?;
81+
write!(dest, "{}", srcloc.line())?;
82+
dest.write_str("]")
83+
},
84+
)?;
85+
spacer.write_always(dest, |dest| dest.write_str(record.payload()))?;
86+
87+
let key_values = record.key_values();
88+
spacer.write_if(self.options.kv && !key_values.is_empty(), dest, |dest| {
89+
dest.write_str("{ ")?;
90+
key_values.write_to(dest, false)?;
91+
dest.write_str(" }")
92+
})?;
93+
94+
if self.options.eol {
95+
dest.write_str(__EOL)?;
96+
}
97+
98+
ctx.set_style_range(style_range);
99+
Ok(())
100+
}
101+
}
102+
103+
impl Formatter for PartialFormatter {
104+
fn format(
105+
&self,
106+
record: &Record,
107+
dest: &mut StringBuf,
108+
ctx: &mut FormatterContext,
109+
) -> crate::Result<()> {
110+
self.format_impl(record, dest, ctx)
111+
.map_err(Error::FormatRecord)
112+
}
113+
}
114+
115+
pub struct PartialFormatterBuilder(FormattingOptions);
116+
117+
impl PartialFormatterBuilder {
118+
#[must_use]
119+
pub fn time(&mut self, value: bool) -> &mut Self {
120+
self.0.time = value;
121+
self
122+
}
123+
124+
#[must_use]
125+
pub fn logger_name(&mut self, value: bool) -> &mut Self {
126+
self.0.logger_name = value;
127+
self
128+
}
129+
130+
#[must_use]
131+
pub fn level(&mut self, value: bool) -> &mut Self {
132+
self.0.level = value;
133+
self
134+
}
135+
136+
#[must_use]
137+
pub fn source_location(&mut self, value: bool) -> &mut Self {
138+
self.0.source_location = value;
139+
self
140+
}
141+
142+
#[must_use]
143+
pub fn kv(&mut self, value: bool) -> &mut Self {
144+
self.0.kv = value;
145+
self
146+
}
147+
148+
#[must_use]
149+
pub fn eol(&mut self, value: bool) -> &mut Self {
150+
self.0.eol = value;
151+
self
152+
}
153+
154+
/// Builds a `PartialFormatter`.
155+
#[must_use]
156+
pub fn build(&mut self) -> PartialFormatter {
157+
PartialFormatter {
158+
options: self.0.clone(),
159+
}
160+
}
161+
}
162+
163+
#[derive(Clone)]
164+
struct FormattingOptions {
165+
time: bool,
166+
logger_name: bool,
167+
level: bool,
168+
source_location: bool,
169+
kv: bool,
170+
eol: bool,
171+
}
172+
173+
struct AutoSpacer(bool);
174+
175+
impl AutoSpacer {
176+
fn new() -> Self {
177+
Self(false)
178+
}
179+
180+
fn write_always(
181+
&mut self,
182+
dest: &mut StringBuf,
183+
f: impl FnOnce(&mut StringBuf) -> fmt::Result,
184+
) -> fmt::Result {
185+
if self.0 {
186+
dest.write_str(" ")?;
187+
} else {
188+
self.0 = true;
189+
}
190+
f(dest)?;
191+
Ok(())
192+
}
193+
194+
fn write_if(
195+
&mut self,
196+
conf: bool,
197+
dest: &mut StringBuf,
198+
f: impl FnOnce(&mut StringBuf) -> fmt::Result,
199+
) -> fmt::Result {
200+
if conf {
201+
if self.0 {
202+
dest.write_str(" ")?;
203+
} else {
204+
self.0 = true;
205+
}
206+
f(dest)?;
207+
}
208+
Ok(())
209+
}
210+
211+
fn write_if_opt<O>(
212+
&mut self,
213+
conf: bool,
214+
option: Option<O>,
215+
dest: &mut StringBuf,
216+
f: impl FnOnce(&mut StringBuf, O) -> fmt::Result,
217+
) -> fmt::Result {
218+
if conf {
219+
if let Some(option) = option {
220+
if self.0 {
221+
dest.write_str(" ")?;
222+
} else {
223+
self.0 = true;
224+
}
225+
f(dest, option)?;
226+
}
227+
}
228+
Ok(())
229+
}
230+
}
231+
232+
#[cfg(test)]
233+
mod tests {
234+
use super::*;
235+
use crate::{formatter::FullFormatter, kv, Level, RecordOwned};
236+
237+
fn record() -> RecordOwned {
238+
let kvs = [
239+
(kv::Key::__from_static_str("k1"), kv::Value::from(114)),
240+
(kv::Key::__from_static_str("k2"), kv::Value::from("514")),
241+
];
242+
Record::new(Level::Warn, "test log content", None, Some("logger"), &kvs).to_owned()
243+
}
244+
245+
#[test]
246+
fn default_should_same_with_full() {
247+
let record = record();
248+
249+
let partial = {
250+
let mut buf = StringBuf::new();
251+
let mut ctx = FormatterContext::new();
252+
PartialFormatter::builder()
253+
.build()
254+
.format(&record.as_ref(), &mut buf, &mut ctx)
255+
.unwrap();
256+
(buf, ctx)
257+
};
258+
let full = {
259+
let mut buf = StringBuf::new();
260+
let mut ctx = FormatterContext::new();
261+
FullFormatter::new()
262+
.format(&record.as_ref(), &mut buf, &mut ctx)
263+
.unwrap();
264+
(buf, ctx)
265+
};
266+
267+
assert_eq!(partial.0, full.0);
268+
assert_eq!(partial.1.style_range(), full.1.style_range());
269+
}
270+
271+
#[test]
272+
fn no_time() {
273+
let record = record();
274+
let mut buf = StringBuf::new();
275+
let mut ctx = FormatterContext::new();
276+
PartialFormatter::builder()
277+
.time(false)
278+
.build()
279+
.format(&record.as_ref(), &mut buf, &mut ctx)
280+
.unwrap();
281+
282+
assert_eq!(
283+
buf,
284+
format!("[logger] [warn] test log content {{ k1=114 k2=514 }}{__EOL}")
285+
);
286+
assert_eq!(ctx.style_range(), Some(10..14));
287+
}
288+
289+
#[test]
290+
fn no_time_logger_name() {
291+
let record = record();
292+
let mut buf = StringBuf::new();
293+
let mut ctx = FormatterContext::new();
294+
PartialFormatter::builder()
295+
.time(false)
296+
.logger_name(false)
297+
.build()
298+
.format(&record.as_ref(), &mut buf, &mut ctx)
299+
.unwrap();
300+
301+
assert_eq!(
302+
buf,
303+
format!("[warn] test log content {{ k1=114 k2=514 }}{__EOL}")
304+
);
305+
assert_eq!(ctx.style_range(), Some(1..5));
306+
}
307+
308+
#[test]
309+
fn no_time_logger_name_level() {
310+
let record = record();
311+
let mut buf = StringBuf::new();
312+
let mut ctx = FormatterContext::new();
313+
PartialFormatter::builder()
314+
.time(false)
315+
.logger_name(false)
316+
.level(false)
317+
.build()
318+
.format(&record.as_ref(), &mut buf, &mut ctx)
319+
.unwrap();
320+
321+
assert_eq!(buf, format!("test log content {{ k1=114 k2=514 }}{__EOL}"));
322+
assert!(ctx.style_range().is_none());
323+
}
324+
325+
#[test]
326+
fn no_time_logger_name_level_kv() {
327+
let record = record();
328+
let mut buf = StringBuf::new();
329+
let mut ctx = FormatterContext::new();
330+
PartialFormatter::builder()
331+
.time(false)
332+
.logger_name(false)
333+
.level(false)
334+
.kv(false)
335+
.build()
336+
.format(&record.as_ref(), &mut buf, &mut ctx)
337+
.unwrap();
338+
339+
assert_eq!(buf, format!("test log content{__EOL}"));
340+
assert!(ctx.style_range().is_none());
341+
}
342+
343+
#[test]
344+
fn no_time_eol() {
345+
let record = record();
346+
let mut buf = StringBuf::new();
347+
let mut ctx = FormatterContext::new();
348+
PartialFormatter::builder()
349+
.time(false)
350+
.eol(false)
351+
.build()
352+
.format(&record.as_ref(), &mut buf, &mut ctx)
353+
.unwrap();
354+
355+
assert_eq!(buf, "[logger] [warn] test log content { k1=114 k2=514 }");
356+
assert_eq!(ctx.style_range(), Some(10..14));
357+
}
358+
}

0 commit comments

Comments
 (0)