diff --git a/examples/simple_stdio.rs b/examples/simple_stdio.rs index 1589851..e29136b 100644 --- a/examples/simple_stdio.rs +++ b/examples/simple_stdio.rs @@ -26,6 +26,12 @@ fn main() { .layout(TextLayout::default()) .append(append::Stdout), ) + .dispatch( + Dispatch::new() + .filter(LevelFilter::Trace) + .layout(TextLayout::default().no_color()) + .append(append::Stderr), + ) .apply() .unwrap(); diff --git a/src/append/fastrace.rs b/src/append/fastrace.rs index 617a510..f252464 100644 --- a/src/append/fastrace.rs +++ b/src/append/fastrace.rs @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::borrow::Cow; + use jiff::Zoned; use log::Record; use crate::append::Append; -use crate::layout::KvDisplay; +use crate::layout::collect_kvs; /// An appender that adds log records to fastrace as an event associated to the current span. #[derive(Default, Debug, Clone)] @@ -24,14 +26,19 @@ pub struct FastraceEvent; impl Append for FastraceEvent { fn append(&self, record: &Record) -> anyhow::Result<()> { - let message = format!( - "{} {:>5} {}{}", - Zoned::now(), - record.level(), - record.args(), - KvDisplay::new(record.key_values()), - ); - fastrace::Event::add_to_local_parent(message, || []); + let message = format!("{}", record.args()); + fastrace::Event::add_to_local_parent(message, || { + [ + (Cow::from("level"), Cow::from(record.level().as_str())), + (Cow::from("timestamp"), Cow::from(Zoned::now().to_string())), + ] + .into_iter() + .chain( + collect_kvs(record.key_values()) + .into_iter() + .map(|(k, v)| (Cow::from(k), Cow::from(v))), + ) + }); Ok(()) } diff --git a/src/layout/kv.rs b/src/layout/kv.rs index a619f34..8f9685b 100644 --- a/src/layout/kv.rs +++ b/src/layout/kv.rs @@ -45,3 +45,25 @@ impl<'a, 'kvs> log::kv::Visitor<'kvs> for KvWriter<'a, 'kvs> { Ok(()) } } + +/// A helper to collect log's key-value pairs. +pub fn collect_kvs(kv: &dyn log::kv::Source) -> Vec<(String, String)> { + let mut collector = KvCollector { kv: Vec::new() }; + kv.visit(&mut collector).ok(); + collector.kv +} + +struct KvCollector { + kv: Vec<(String, String)>, +} + +impl<'kvs> log::kv::Visitor<'kvs> for KvCollector { + fn visit_pair( + &mut self, + key: log::kv::Key<'kvs>, + value: log::kv::Value<'kvs>, + ) -> Result<(), log::kv::Error> { + self.kv.push((key.to_string(), value.to_string())); + Ok(()) + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index de9d092..5ac9a21 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -18,6 +18,7 @@ pub use custom::CustomLayout; pub use identical::IdenticalLayout; #[cfg(feature = "json")] pub use json::JsonLayout; +pub use kv::collect_kvs; pub use kv::KvDisplay; pub use text::LevelColor; pub use text::TextLayout; diff --git a/src/layout/text.rs b/src/layout/text.rs index e06d683..9b5f1da 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -37,7 +37,7 @@ use crate::layout::Layout; /// ``` /// /// By default, log levels are colored. You can turn on the `no-color` feature flag to disable this -/// feature. +/// feature. Instead, you can also set the `no_color` field to `true` to disable coloring. /// /// You can also customize the color of each log level by setting the `colors` field with a /// [`LevelColor`] instance. @@ -47,9 +47,66 @@ use crate::layout::Layout; #[derive(Default, Debug, Clone)] pub struct TextLayout { pub colors: LevelColor, + pub no_color: bool, pub tz: Option, } +impl TextLayout { + /// Turn off coloring of log levels. + pub fn no_color(mut self) -> Self { + self.no_color = true; + self + } + + /// Set the timezone of the timestamp; default to the system timezone. + pub fn timezone(mut self, tz: TimeZone) -> Self { + self.tz = Some(tz); + self + } + + /// Customize the color of each log level; no effect if `no_color` is set to `true`, + /// or the `no-color` feature flag is enabled. + pub fn colors(mut self, colors: LevelColor) -> Self { + self.colors = colors; + self + } + + /// Customize the color of the error log level; no effect if `no_color` is set to `true`, + /// or the `no-color` feature flag is enabled. Default to red. + pub fn error_color(mut self, color: Color) -> Self { + self.colors.error = color; + self + } + + /// Customize the color of the warn log level; no effect if `no_color` is set to `true`, + /// or the `no-color` feature flag is enabled. Default to yellow. + pub fn warn_color(mut self, color: Color) -> Self { + self.colors.warn = color; + self + } + + /// Customize the color of the info log level; no effect if `no_color` is set to `true`, + /// or the `no-color` feature flag is enabled. Default to green. + pub fn info_color(mut self, color: Color) -> Self { + self.colors.info = color; + self + } + + /// Customize the color of the debug log level; no effect if `no_color` is set to `true`, + /// or the `no-color` feature flag is enabled. Default to blue. + pub fn debug_color(mut self, color: Color) -> Self { + self.colors.debug = color; + self + } + + /// Customize the color of the trace log level; no effect if `no_color` is set to `true`, + /// or the `no-color` feature flag is enabled. Default to magenta. + pub fn trace_color(mut self, color: Color) -> Self { + self.colors.trace = color; + self + } +} + /// Customize the color of each log level. #[derive(Debug, Clone)] pub struct LevelColor { @@ -77,20 +134,23 @@ impl TextLayout { where F: Fn(Arguments) -> anyhow::Result<()>, { - let color = match record.level() { - Level::Error => self.colors.error, - Level::Warn => self.colors.warn, - Level::Info => self.colors.info, - Level::Debug => self.colors.debug, - Level::Trace => self.colors.trace, - }; - let time = match self.tz.clone() { Some(tz) => Zoned::now().with_time_zone(tz), None => Zoned::now(), } .strftime("%Y-%m-%dT%H:%M:%S.%6f%:z"); - let level = ColoredString::from(record.level().to_string()).color(color); + let level = if self.no_color { + ColoredString::from(record.level().to_string()) + } else { + let color = match record.level() { + Level::Error => self.colors.error, + Level::Warn => self.colors.warn, + Level::Info => self.colors.info, + Level::Debug => self.colors.debug, + Level::Trace => self.colors.trace, + }; + ColoredString::from(record.level().to_string()).color(color) + }; let module = record.module_path().unwrap_or_default(); let file = record.file().unwrap_or_default(); let line = record.line().unwrap_or_default();