Skip to content

Commit c56f465

Browse files
committed
Add formatter builder
1 parent 304207b commit c56f465

File tree

3 files changed

+161
-25
lines changed

3 files changed

+161
-25
lines changed

src/builders.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ use log::Log;
1515

1616
use crate::{log_impl, Filter, FormatCallback, Formatter};
1717

18+
#[cfg(feature = "colored")]
19+
use crate::colors::ColoredLevelConfig;
20+
1821
#[cfg(feature = "date-based")]
1922
use crate::log_impl::DateBasedState;
2023

@@ -1230,3 +1233,141 @@ impl From<DateBased> for Output {
12301233
})))
12311234
}
12321235
}
1236+
1237+
/// A generic formatter that easily provides good defaults while being configurable
1238+
pub struct FormatterBuilder {
1239+
#[cfg(feature = "colored")]
1240+
color_config: Option<ColoredLevelConfig>,
1241+
#[cfg(feature = "chrono")]
1242+
chrono: bool,
1243+
level: bool,
1244+
target: bool,
1245+
}
1246+
1247+
impl Default for FormatterBuilder {
1248+
fn default() -> Self {
1249+
FormatterBuilder {
1250+
#[cfg(feature = "colored")]
1251+
color_config: Some(Default::default()),
1252+
#[cfg(feature = "chrono")]
1253+
chrono: true,
1254+
level: true,
1255+
target: true,
1256+
}
1257+
}
1258+
}
1259+
1260+
impl FormatterBuilder {
1261+
pub fn new() -> Self {
1262+
Self::default()
1263+
}
1264+
1265+
pub fn level(mut self, level: bool) -> Self {
1266+
self.level = level;
1267+
self
1268+
}
1269+
1270+
pub fn target(mut self, target: bool) -> Self {
1271+
self.target = target;
1272+
self
1273+
}
1274+
1275+
#[cfg(feature = "colored")]
1276+
pub fn color(mut self, color: bool) -> Self {
1277+
self.color_config = if color {
1278+
self.color_config.or_else(Default::default)
1279+
} else {
1280+
None
1281+
};
1282+
self
1283+
}
1284+
1285+
#[cfg(feature = "colored")]
1286+
pub fn color_config(
1287+
mut self,
1288+
modify_config: impl FnOnce(ColoredLevelConfig) -> ColoredLevelConfig,
1289+
) -> Self {
1290+
self.color_config = self.color_config.map(modify_config);
1291+
self
1292+
}
1293+
1294+
#[cfg(feature = "chrono")]
1295+
pub fn chrono(mut self, chrono: bool) -> Self {
1296+
self.chrono = chrono;
1297+
self
1298+
}
1299+
1300+
#[rustfmt::skip]
1301+
pub fn build(
1302+
self,
1303+
) -> impl Fn(FormatCallback<'_>, &std::fmt::Arguments<'_>, &log::Record<'_>) + Sync + Send + 'static
1304+
{
1305+
move |out, message, record| {
1306+
/* Type checking is hard */
1307+
#[cfg(not(feature = "chrono"))]
1308+
type TimeType = String;
1309+
#[cfg(feature = "chrono")]
1310+
type TimeType<'a> = chrono::format::DelayedFormat<chrono::format::StrftimeItems<'a>>;
1311+
1312+
let time: Option<TimeType> = {
1313+
#[cfg(feature = "chrono")]
1314+
{
1315+
self.chrono
1316+
.then(|| chrono::Local::now().format("%Y-%m-%d %H:%M:%S,%3f"))
1317+
}
1318+
#[cfg(not(feature = "chrono"))]
1319+
{
1320+
None
1321+
}
1322+
};
1323+
1324+
/* Type checking is hard */
1325+
#[cfg(not(feature = "colored"))]
1326+
type LevelType = log::Level;
1327+
#[cfg(feature = "colored")]
1328+
type LevelType = crate::colors::WithFgColor<log::Level>;
1329+
1330+
let level: Option<LevelType> = if self.level {
1331+
#[cfg(feature = "colored")]
1332+
{
1333+
Some(
1334+
self.color_config
1335+
.map(|config| config.color(record.level()))
1336+
.unwrap_or_else(|| crate::colors::WithFgColor {
1337+
text: record.level(),
1338+
color: None,
1339+
}),
1340+
)
1341+
}
1342+
#[cfg(not(feature = "colored"))]
1343+
{
1344+
Some(record.level())
1345+
}
1346+
} else {
1347+
None
1348+
};
1349+
1350+
let target = self.target.then(|| {
1351+
if !record.target().is_empty() {
1352+
record.target()
1353+
} else {
1354+
record.module_path().unwrap_or_default()
1355+
}
1356+
});
1357+
1358+
/* Sadly we cannot store and compose std::fmt::Arguments due to lifetime issues.
1359+
* In order to avoid unnecessary write calls nevertheless, we must enumerate all options
1360+
*/
1361+
match (time, level, target) {
1362+
(Some(time), Some(level), Some(target)) => out.finish(format_args!("{} {:<5} [{}] {}", time, level, target, message)),
1363+
(Some(time), Some(level), None) => out.finish(format_args!("{} {:<5} {}", time, level, message)),
1364+
(Some(time), None, Some(target)) => out.finish(format_args!("{} [{}] {}", time, target, message)),
1365+
(Some(time), None, None) => out.finish(format_args!("{} {}", time, message)),
1366+
(None, Some(level), Some(target)) => out.finish(format_args!("{:<5} [{}] {}", level, target, message)),
1367+
(None, None, Some(target)) => out.finish(format_args!("[{}] {}", target, message)),
1368+
(None, Some(level), None) => out.finish(format_args!("{:<5} {}", level, message)),
1369+
(None, None, None) => out.finish(format_args!("{}", message)),
1370+
}
1371+
}
1372+
}
1373+
}

src/colors.rs

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,22 @@ pub struct WithFgColor<T>
8282
where
8383
T: fmt::Display,
8484
{
85-
text: T,
86-
color: Color,
85+
pub(super) text: T,
86+
pub(super) color: Option<Color>,
8787
}
8888

8989
impl<T> fmt::Display for WithFgColor<T>
9090
where
9191
T: fmt::Display,
9292
{
9393
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94-
write!(f, "\x1B[{}m", self.color.to_fg_str())?;
95-
fmt::Display::fmt(&self.text, f)?;
96-
write!(f, "\x1B[0m")?;
94+
if let Some(color) = self.color {
95+
write!(f, "\x1B[{}m", color.to_fg_str())?;
96+
fmt::Display::fmt(&self.text, f)?;
97+
write!(f, "\x1B[0m")?;
98+
} else {
99+
fmt::Display::fmt(&self.text, f)?;
100+
}
97101
Ok(())
98102
}
99103
}
@@ -246,26 +250,17 @@ impl ColoredLevelConfig {
246250
impl Default for ColoredLevelConfig {
247251
/// Retrieves the default configuration. This has:
248252
///
249-
/// - [`Error`] as [`Color::Red`]
250-
/// - [`Warn`] as [`Color::Yellow`]
251-
/// - [`Info`] as [`Color::White`]
252-
/// - [`Debug`] as [`Color::White`]
253-
/// - [`Trace`] as [`Color::White`]
254-
///
255-
/// [`Error`]: https://docs.rs/log/0.4/log/enum.Level.html#variant.Error
256-
/// [`Warn`]: https://docs.rs/log/0.4/log/enum.Level.html#variant.Warn
257-
/// [`Info`]: https://docs.rs/log/0.4/log/enum.Level.html#variant.Info
258-
/// [`Debug`]: https://docs.rs/log/0.4/log/enum.Level.html#variant.Debug
259-
/// [`Trace`]: https://docs.rs/log/0.4/log/enum.Level.html#variant.Trace
260-
/// [`Color::White`]: https://docs.rs/colored/1/colored/enum.Color.html#variant.White
261-
/// [`Color::Yellow`]: https://docs.rs/colored/1/colored/enum.Color.html#variant.Yellow
262-
/// [`Color::Red`]: https://docs.rs/colored/1/colored/enum.Color.html#variant.Red
253+
/// - [`Error`](log::Level::Error) as [`Color::Red`]
254+
/// - [`Warn`](log::Level::Warn) as [`Color::Yellow`]
255+
/// - [`Info`](log::Level::Info) as [`Color::White`]
256+
/// - [`Debug`](log::Level::Debug) as [`Color::White`]
257+
/// - [`Trace`](log::Level::Trace) as [`Color::White`]
263258
fn default() -> Self {
264259
ColoredLevelConfig {
265260
error: Color::Red,
266261
warn: Color::Yellow,
267-
debug: Color::White,
268-
info: Color::White,
262+
info: Color::Cyan,
263+
debug: Color::BrightMagenta,
269264
trace: Color::White,
270265
}
271266
}
@@ -275,7 +270,7 @@ impl ColoredLogLevel for Level {
275270
fn colored(&self, color: Color) -> WithFgColor<Level> {
276271
WithFgColor {
277272
text: *self,
278-
color: color,
273+
color: Some(color),
279274
}
280275
}
281276
}
@@ -313,7 +308,7 @@ mod test {
313308
"{}",
314309
WithFgColor {
315310
text: "test",
316-
color: color,
311+
color: Some(color),
317312
}
318313
)
319314
);
@@ -327,7 +322,7 @@ mod test {
327322
"{:^8}",
328323
WithFgColor {
329324
text: "test",
330-
color: Yellow,
325+
color: Some(Yellow),
331326
}
332327
);
333328
assert!(s.contains(" test "));

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ use std::{
229229
use std::collections::HashMap;
230230

231231
pub use crate::{
232-
builders::{Dispatch, Output},
232+
builders::{Dispatch, FormatterBuilder, Output},
233233
errors::InitError,
234234
log_impl::FormatCallback,
235235
};

0 commit comments

Comments
 (0)