use std::fmt::{self, Write as _};
use crate::{
formatter::{fmt_with_time, Formatter, FormatterContext, TimeDate},
Error, Record, StringBuf, __EOL,
};
#[rustfmt::skip]
#[doc = include_str!(concat!(env!("OUT_DIR"), "/test_utils/common_for_doc_test.rs"))]
#[derive(Clone)]
pub struct FullFormatter {
options: FormattingOptions,
}
impl FullFormatter {
#[must_use]
pub fn new() -> FullFormatter {
Self::builder().build()
}
#[must_use]
pub fn builder() -> FullFormatterBuilder {
FullFormatterBuilder(FormattingOptions {
time: true,
logger_name: true,
level: true,
source_location: true,
kv: true,
eol: true,
})
}
fn format_impl(
&self,
record: &Record,
dest: &mut StringBuf,
ctx: &mut FormatterContext,
) -> Result<(), fmt::Error> {
#[cfg(not(feature = "flexible-string"))]
dest.reserve(crate::string_buf::RESERVE_SIZE);
let mut spacer = AutoSpacer::new();
spacer.write_if(self.options.time, dest, |dest| {
fmt_with_time(
ctx,
record,
|mut time: TimeDate| -> Result<(), fmt::Error> {
dest.write_str("[")?;
dest.write_str(time.full_second_str())?;
dest.write_str(".")?;
write!(dest, "{:03}", time.millisecond())?;
dest.write_str("]")?;
Ok(())
},
)
})?;
spacer.write_if_opt(
self.options.logger_name,
record.logger_name(),
dest,
|dest, logger_name| {
dest.write_str("[")?;
dest.write_str(logger_name)?;
dest.write_str("]")
},
)?;
let mut style_range = None;
spacer.write_if(self.options.level, dest, |dest| {
dest.write_str("[")?;
let style_range_begin = dest.len();
dest.write_str(record.level().as_str())?;
let style_range_end = dest.len();
dest.write_str("]")?;
style_range = Some(style_range_begin..style_range_end);
Ok(())
})?;
spacer.write_if_opt(
self.options.source_location,
record.source_location(),
dest,
|dest, srcloc| {
dest.write_str("[")?;
dest.write_str(srcloc.module_path())?;
dest.write_str(", ")?;
dest.write_str(srcloc.file())?;
dest.write_str(":")?;
write!(dest, "{}", srcloc.line())?;
dest.write_str("]")
},
)?;
spacer.write_always(dest, |dest| dest.write_str(record.payload()))?;
let key_values = record.key_values();
spacer.write_if(self.options.kv && !key_values.is_empty(), dest, |dest| {
dest.write_str("{ ")?;
key_values.write_to(dest, false)?;
dest.write_str(" }")
})?;
if self.options.eol {
dest.write_str(__EOL)?;
}
ctx.set_style_range(style_range);
Ok(())
}
}
impl Formatter for FullFormatter {
fn format(
&self,
record: &Record,
dest: &mut StringBuf,
ctx: &mut FormatterContext,
) -> crate::Result<()> {
self.format_impl(record, dest, ctx)
.map_err(Error::FormatRecord)
}
}
impl Default for FullFormatter {
fn default() -> FullFormatter {
FullFormatter::new()
}
}
#[allow(missing_docs)]
pub struct FullFormatterBuilder(FormattingOptions);
impl FullFormatterBuilder {
#[must_use]
pub fn time(&mut self, value: bool) -> &mut Self {
self.0.time = value;
self
}
#[must_use]
pub fn logger_name(&mut self, value: bool) -> &mut Self {
self.0.logger_name = value;
self
}
#[must_use]
pub fn level(&mut self, value: bool) -> &mut Self {
self.0.level = value;
self
}
#[must_use]
pub fn source_location(&mut self, value: bool) -> &mut Self {
self.0.source_location = value;
self
}
#[must_use]
pub fn kv(&mut self, value: bool) -> &mut Self {
self.0.kv = value;
self
}
#[must_use]
pub fn eol(&mut self, value: bool) -> &mut Self {
self.0.eol = value;
self
}
#[must_use]
pub fn build(&mut self) -> FullFormatter {
FullFormatter {
options: self.0.clone(),
}
}
}
#[derive(Clone)]
struct FormattingOptions {
time: bool,
logger_name: bool,
level: bool,
source_location: bool,
kv: bool,
eol: bool,
}
struct AutoSpacer(bool);
impl AutoSpacer {
fn new() -> Self {
Self(false)
}
fn write_always(
&mut self,
dest: &mut StringBuf,
f: impl FnOnce(&mut StringBuf) -> fmt::Result,
) -> fmt::Result {
if self.0 {
dest.write_str(" ")?;
} else {
self.0 = true;
}
f(dest)?;
Ok(())
}
fn write_if(
&mut self,
conf: bool,
dest: &mut StringBuf,
f: impl FnOnce(&mut StringBuf) -> fmt::Result,
) -> fmt::Result {
if conf {
if self.0 {
dest.write_str(" ")?;
} else {
self.0 = true;
}
f(dest)?;
}
Ok(())
}
fn write_if_opt<O>(
&mut self,
conf: bool,
option: Option<O>,
dest: &mut StringBuf,
f: impl FnOnce(&mut StringBuf, O) -> fmt::Result,
) -> fmt::Result {
if conf {
if let Some(option) = option {
if self.0 {
dest.write_str(" ")?;
} else {
self.0 = true;
}
f(dest, option)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use chrono::prelude::*;
use super::*;
use crate::{kv, Level, RecordOwned, __EOL};
fn record() -> RecordOwned {
let kvs = [
(kv::Key::__from_static_str("k1"), kv::Value::from(114)),
(kv::Key::__from_static_str("k2"), kv::Value::from("514")),
];
Record::new(Level::Warn, "test log content", None, Some("logger"), &kvs).to_owned()
}
#[test]
fn format() {
let record = record();
let record = record.as_ref();
let mut buf = StringBuf::new();
let mut ctx = FormatterContext::new();
FullFormatter::new()
.format(&record, &mut buf, &mut ctx)
.unwrap();
let local_time: DateTime<Local> = record.time().into();
assert_eq!(
format!(
"[{}] [logger] [warn] test log content {{ k1=114 k2=514 }}{}",
local_time.format("%Y-%m-%d %H:%M:%S.%3f"),
__EOL
),
buf
);
assert_eq!(Some(36..40), ctx.style_range());
}
#[test]
fn no_time() {
let record = record();
let mut buf = StringBuf::new();
let mut ctx = FormatterContext::new();
FullFormatter::builder()
.time(false)
.build()
.format(&record.as_ref(), &mut buf, &mut ctx)
.unwrap();
assert_eq!(
buf,
format!("[logger] [warn] test log content {{ k1=114 k2=514 }}{__EOL}")
);
assert_eq!(ctx.style_range(), Some(10..14));
}
#[test]
fn no_time_logger_name() {
let record = record();
let mut buf = StringBuf::new();
let mut ctx = FormatterContext::new();
FullFormatter::builder()
.time(false)
.logger_name(false)
.build()
.format(&record.as_ref(), &mut buf, &mut ctx)
.unwrap();
assert_eq!(
buf,
format!("[warn] test log content {{ k1=114 k2=514 }}{__EOL}")
);
assert_eq!(ctx.style_range(), Some(1..5));
}
#[test]
fn no_time_logger_name_level() {
let record = record();
let mut buf = StringBuf::new();
let mut ctx = FormatterContext::new();
FullFormatter::builder()
.time(false)
.logger_name(false)
.level(false)
.build()
.format(&record.as_ref(), &mut buf, &mut ctx)
.unwrap();
assert_eq!(buf, format!("test log content {{ k1=114 k2=514 }}{__EOL}"));
assert!(ctx.style_range().is_none());
}
#[test]
fn no_time_logger_name_level_kv() {
let record = record();
let mut buf = StringBuf::new();
let mut ctx = FormatterContext::new();
FullFormatter::builder()
.time(false)
.logger_name(false)
.level(false)
.kv(false)
.build()
.format(&record.as_ref(), &mut buf, &mut ctx)
.unwrap();
assert_eq!(buf, format!("test log content{__EOL}"));
assert!(ctx.style_range().is_none());
}
#[test]
fn no_time_eol() {
let record = record();
let mut buf = StringBuf::new();
let mut ctx = FormatterContext::new();
FullFormatter::builder()
.time(false)
.eol(false)
.build()
.format(&record.as_ref(), &mut buf, &mut ctx)
.unwrap();
assert_eq!(buf, "[logger] [warn] test log content { k1=114 k2=514 }");
assert_eq!(ctx.style_range(), Some(10..14));
}
}