1use std::fmt::{self, Write};
4
5use crate::{
6 formatter::{fmt_with_time, Formatter, FormatterContext, TimeDate},
7 Error, Record, StringBuf, __EOL,
8};
9
10#[rustfmt::skip]
11#[doc = include_str!(concat!(env!("OUT_DIR"), "/test_utils/common_for_doc_test.rs"))]
46#[derive(Clone)]
74pub struct FullFormatter {
75 options: FormattingOptions,
76}
77
78impl FullFormatter {
79 #[must_use]
83 pub fn new() -> FullFormatter {
84 Self::builder().build()
85 }
86
87 #[must_use]
105 pub fn builder() -> FullFormatterBuilder {
106 FullFormatterBuilder(FormattingOptions {
107 time: true,
108 logger_name: true,
109 level: true,
110 source_location: true,
111 kv: true,
112 eol: true,
113 })
114 }
115
116 fn format_impl(
117 &self,
118 record: &Record,
119 dest: &mut StringBuf,
120 ctx: &mut FormatterContext,
121 ) -> Result<(), fmt::Error> {
122 #[cfg(not(feature = "flexible-string"))]
123 dest.reserve(crate::string_buf::RESERVE_SIZE);
124
125 let mut spacer = AutoSpacer::new();
126
127 spacer.write_if(self.options.time, dest, |dest| {
128 fmt_with_time(
129 ctx,
130 record,
131 |mut time: TimeDate| -> Result<(), fmt::Error> {
132 dest.write_str("[")?;
133 dest.write_str(time.full_second_str())?;
134 dest.write_str(".")?;
135 write!(dest, "{:03}", time.millisecond())?;
136 dest.write_str("]")?;
137 Ok(())
138 },
139 )
140 })?;
141 spacer.write_if_opt(
142 self.options.logger_name,
143 record.logger_name(),
144 dest,
145 |dest, logger_name| {
146 dest.write_str("[")?;
147 dest.write_str(logger_name)?;
148 dest.write_str("]")
149 },
150 )?;
151 let mut style_range = None;
152 spacer.write_if(self.options.level, dest, |dest| {
153 dest.write_str("[")?;
154 let style_range_begin = dest.len();
155 dest.write_str(record.level().as_str())?;
156 let style_range_end = dest.len();
157 dest.write_str("]")?;
158 style_range = Some(style_range_begin..style_range_end);
159 Ok(())
160 })?;
161 spacer.write_if_opt(
162 self.options.source_location,
163 record.source_location(),
164 dest,
165 |dest, srcloc| {
166 dest.write_str("[")?;
167 dest.write_str(srcloc.module_path())?;
168 dest.write_str(", ")?;
169 dest.write_str(srcloc.file())?;
170 dest.write_str(":")?;
171 write!(dest, "{}", srcloc.line())?;
172 dest.write_str("]")
173 },
174 )?;
175 spacer.write_always(dest, |dest| dest.write_str(record.payload()))?;
176
177 let key_values = record.key_values();
178 spacer.write_if(self.options.kv && !key_values.is_empty(), dest, |dest| {
179 dest.write_str("{ ")?;
180 key_values.write_to(dest, false)?;
181 dest.write_str(" }")
182 })?;
183
184 if self.options.eol {
185 dest.write_str(__EOL)?;
186 }
187
188 ctx.set_style_range(style_range);
189 Ok(())
190 }
191}
192
193impl Formatter for FullFormatter {
194 fn format(
195 &self,
196 record: &Record,
197 dest: &mut StringBuf,
198 ctx: &mut FormatterContext,
199 ) -> crate::Result<()> {
200 self.format_impl(record, dest, ctx)
201 .map_err(Error::FormatRecord)
202 }
203}
204
205impl Default for FullFormatter {
206 fn default() -> FullFormatter {
207 FullFormatter::new()
208 }
209}
210
211#[allow(missing_docs)]
212pub struct FullFormatterBuilder(FormattingOptions);
213
214impl FullFormatterBuilder {
215 #[must_use]
219 pub fn time(&mut self, value: bool) -> &mut Self {
220 self.0.time = value;
221 self
222 }
223
224 #[must_use]
228 pub fn logger_name(&mut self, value: bool) -> &mut Self {
229 self.0.logger_name = value;
230 self
231 }
232
233 #[must_use]
240 pub fn level(&mut self, value: bool) -> &mut Self {
241 self.0.level = value;
242 self
243 }
244
245 #[must_use]
249 pub fn source_location(&mut self, value: bool) -> &mut Self {
250 self.0.source_location = value;
251 self
252 }
253
254 #[must_use]
258 pub fn kv(&mut self, value: bool) -> &mut Self {
259 self.0.kv = value;
260 self
261 }
262
263 #[must_use]
267 pub fn eol(&mut self, value: bool) -> &mut Self {
268 self.0.eol = value;
269 self
270 }
271
272 #[must_use]
274 pub fn build(&mut self) -> FullFormatter {
275 FullFormatter {
276 options: self.0.clone(),
277 }
278 }
279}
280
281#[derive(Clone)]
282struct FormattingOptions {
283 time: bool,
284 logger_name: bool,
285 level: bool,
286 source_location: bool,
287 kv: bool,
288 eol: bool,
289}
290
291struct AutoSpacer(bool);
292
293impl AutoSpacer {
294 fn new() -> Self {
295 Self(false)
296 }
297
298 fn write_always(
299 &mut self,
300 dest: &mut StringBuf,
301 f: impl FnOnce(&mut StringBuf) -> fmt::Result,
302 ) -> fmt::Result {
303 if self.0 {
304 dest.write_str(" ")?;
305 } else {
306 self.0 = true;
307 }
308 f(dest)?;
309 Ok(())
310 }
311
312 fn write_if(
313 &mut self,
314 conf: bool,
315 dest: &mut StringBuf,
316 f: impl FnOnce(&mut StringBuf) -> fmt::Result,
317 ) -> fmt::Result {
318 if conf {
319 if self.0 {
320 dest.write_str(" ")?;
321 } else {
322 self.0 = true;
323 }
324 f(dest)?;
325 }
326 Ok(())
327 }
328
329 fn write_if_opt<O>(
330 &mut self,
331 conf: bool,
332 option: Option<O>,
333 dest: &mut StringBuf,
334 f: impl FnOnce(&mut StringBuf, O) -> fmt::Result,
335 ) -> fmt::Result {
336 if conf {
337 if let Some(option) = option {
338 if self.0 {
339 dest.write_str(" ")?;
340 } else {
341 self.0 = true;
342 }
343 f(dest, option)?;
344 }
345 }
346 Ok(())
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use chrono::prelude::*;
353
354 use super::*;
355 use crate::{kv, Level, RecordOwned, __EOL};
356
357 fn record() -> RecordOwned {
358 let kvs = [
359 (kv::Key::__from_static_str("k1"), kv::Value::from(114)),
360 (kv::Key::__from_static_str("k2"), kv::Value::from("514")),
361 ];
362 Record::new(Level::Warn, "test log content", None, Some("logger"), &kvs).to_owned()
363 }
364
365 #[test]
366 fn format() {
367 let record = record();
368 let record = record.as_ref();
369 let mut buf = StringBuf::new();
370 let mut ctx = FormatterContext::new();
371 FullFormatter::new()
372 .format(&record, &mut buf, &mut ctx)
373 .unwrap();
374
375 let local_time: DateTime<Local> = record.time().into();
376 assert_eq!(
377 format!(
378 "[{}] [logger] [warn] test log content {{ k1=114 k2=514 }}{}",
379 local_time.format("%Y-%m-%d %H:%M:%S.%3f"),
380 __EOL
381 ),
382 buf
383 );
384 assert_eq!(Some(36..40), ctx.style_range());
385 }
386
387 #[test]
388 fn no_time() {
389 let record = record();
390 let mut buf = StringBuf::new();
391 let mut ctx = FormatterContext::new();
392 FullFormatter::builder()
393 .time(false)
394 .build()
395 .format(&record.as_ref(), &mut buf, &mut ctx)
396 .unwrap();
397
398 assert_eq!(
399 buf,
400 format!("[logger] [warn] test log content {{ k1=114 k2=514 }}{__EOL}")
401 );
402 assert_eq!(ctx.style_range(), Some(10..14));
403 }
404
405 #[test]
406 fn no_time_logger_name() {
407 let record = record();
408 let mut buf = StringBuf::new();
409 let mut ctx = FormatterContext::new();
410 FullFormatter::builder()
411 .time(false)
412 .logger_name(false)
413 .build()
414 .format(&record.as_ref(), &mut buf, &mut ctx)
415 .unwrap();
416
417 assert_eq!(
418 buf,
419 format!("[warn] test log content {{ k1=114 k2=514 }}{__EOL}")
420 );
421 assert_eq!(ctx.style_range(), Some(1..5));
422 }
423
424 #[test]
425 fn no_time_logger_name_level() {
426 let record = record();
427 let mut buf = StringBuf::new();
428 let mut ctx = FormatterContext::new();
429 FullFormatter::builder()
430 .time(false)
431 .logger_name(false)
432 .level(false)
433 .build()
434 .format(&record.as_ref(), &mut buf, &mut ctx)
435 .unwrap();
436
437 assert_eq!(buf, format!("test log content {{ k1=114 k2=514 }}{__EOL}"));
438 assert!(ctx.style_range().is_none());
439 }
440
441 #[test]
442 fn no_time_logger_name_level_kv() {
443 let record = record();
444 let mut buf = StringBuf::new();
445 let mut ctx = FormatterContext::new();
446 FullFormatter::builder()
447 .time(false)
448 .logger_name(false)
449 .level(false)
450 .kv(false)
451 .build()
452 .format(&record.as_ref(), &mut buf, &mut ctx)
453 .unwrap();
454
455 assert_eq!(buf, format!("test log content{__EOL}"));
456 assert!(ctx.style_range().is_none());
457 }
458
459 #[test]
460 fn no_time_eol() {
461 let record = record();
462 let mut buf = StringBuf::new();
463 let mut ctx = FormatterContext::new();
464 FullFormatter::builder()
465 .time(false)
466 .eol(false)
467 .build()
468 .format(&record.as_ref(), &mut buf, &mut ctx)
469 .unwrap();
470
471 assert_eq!(buf, "[logger] [warn] test log content { k1=114 k2=514 }");
472 assert_eq!(ctx.style_range(), Some(10..14));
473 }
474}