use super::*;
use crate::{
field::{VisitFmt, VisitOutput},
fmt::fmt_layer::{FmtContext, FormattedFields},
registry::LookupSpan,
};
use std::fmt;
use tracing_core::{
field::{self, Field},
Event, Level, Subscriber,
};
#[cfg(feature = "tracing-log")]
use tracing_log::NormalizeEvent;
use nu_ansi_term::{Color, Style};
/// An excessively pretty, human-readable event formatter.
///
/// Unlike the [`Full`], [`Compact`], and [`Json`] formatters, this is a
/// multi-line output format. Each individual event may output multiple lines of
/// text.
///
/// # Example Output
///
///
:; cargo run --example fmt-pretty
/// Finished dev [unoptimized + debuginfo] target(s) in 0.08s
/// Running `target/debug/examples/fmt-pretty`
/// 2022-02-15T18:44:24.535324Z INFO fmt_pretty: preparing to shave yaks, number_of_yaks: 3
/// at examples/examples/fmt-pretty.rs:16 on main
///
/// 2022-02-15T18:44:24.535403Z INFO fmt_pretty::yak_shave: shaving yaks
/// at examples/examples/fmt/yak_shave.rs:41 on main
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535442Z TRACE fmt_pretty::yak_shave: hello! I'm gonna shave a yak, excitement: "yay!"
/// at examples/examples/fmt/yak_shave.rs:16 on main
/// in fmt_pretty::yak_shave::shave with yak: 1
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535469Z TRACE fmt_pretty::yak_shave: yak shaved successfully
/// at examples/examples/fmt/yak_shave.rs:25 on main
/// in fmt_pretty::yak_shave::shave with yak: 1
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535502Z DEBUG yak_events: yak: 1, shaved: true
/// at examples/examples/fmt/yak_shave.rs:46 on main
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535524Z TRACE fmt_pretty::yak_shave: yaks_shaved: 1
/// at examples/examples/fmt/yak_shave.rs:55 on main
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535551Z TRACE fmt_pretty::yak_shave: hello! I'm gonna shave a yak, excitement: "yay!"
/// at examples/examples/fmt/yak_shave.rs:16 on main
/// in fmt_pretty::yak_shave::shave with yak: 2
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535573Z TRACE fmt_pretty::yak_shave: yak shaved successfully
/// at examples/examples/fmt/yak_shave.rs:25 on main
/// in fmt_pretty::yak_shave::shave with yak: 2
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535600Z DEBUG yak_events: yak: 2, shaved: true
/// at examples/examples/fmt/yak_shave.rs:46 on main
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535618Z TRACE fmt_pretty::yak_shave: yaks_shaved: 2
/// at examples/examples/fmt/yak_shave.rs:55 on main
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535644Z TRACE fmt_pretty::yak_shave: hello! I'm gonna shave a yak, excitement: "yay!"
/// at examples/examples/fmt/yak_shave.rs:16 on main
/// in fmt_pretty::yak_shave::shave with yak: 3
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535670Z WARN fmt_pretty::yak_shave: could not locate yak
/// at examples/examples/fmt/yak_shave.rs:18 on main
/// in fmt_pretty::yak_shave::shave with yak: 3
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535698Z DEBUG yak_events: yak: 3, shaved: false
/// at examples/examples/fmt/yak_shave.rs:46 on main
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535720Z ERROR fmt_pretty::yak_shave: failed to shave yak, yak: 3, error: missing yak, error.sources: [out of space, out of cash]
/// at examples/examples/fmt/yak_shave.rs:51 on main
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535742Z TRACE fmt_pretty::yak_shave: yaks_shaved: 2
/// at examples/examples/fmt/yak_shave.rs:55 on main
/// in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
///
/// 2022-02-15T18:44:24.535765Z INFO fmt_pretty: yak shaving completed, all_yaks_shaved: false
/// at examples/examples/fmt-pretty.rs:19 on main
///
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Pretty {
display_location: bool,
}
/// The [visitor] produced by [`Pretty`]'s [`MakeVisitor`] implementation.
///
/// [visitor]: field::Visit
/// [`MakeVisitor`]: crate::field::MakeVisitor
#[derive(Debug)]
pub struct PrettyVisitor<'a> {
writer: Writer<'a>,
is_empty: bool,
style: Style,
result: fmt::Result,
}
/// An excessively pretty, human-readable [`MakeVisitor`] implementation.
///
/// [`MakeVisitor`]: crate::field::MakeVisitor
#[derive(Debug)]
pub struct PrettyFields {
/// A value to override the provided `Writer`'s ANSI formatting
/// configuration.
///
/// If this is `Some`, we override the `Writer`'s ANSI setting. This is
/// necessary in order to continue supporting the deprecated
/// `PrettyFields::with_ansi` method. If it is `None`, we don't override the
/// ANSI formatting configuration (because the deprecated method was not
/// called).
// TODO: when `PrettyFields::with_ansi` is removed, we can get rid
// of this entirely.
ansi: Option,
}
// === impl Pretty ===
impl Default for Pretty {
fn default() -> Self {
Self {
display_location: true,
}
}
}
impl Pretty {
fn style_for(level: &Level) -> Style {
match *level {
Level::TRACE => Style::new().fg(Color::Purple),
Level::DEBUG => Style::new().fg(Color::Blue),
Level::INFO => Style::new().fg(Color::Green),
Level::WARN => Style::new().fg(Color::Yellow),
Level::ERROR => Style::new().fg(Color::Red),
}
}
/// Sets whether the event's source code location is displayed.
///
/// This defaults to `true`.
#[deprecated(
since = "0.3.6",
note = "all formatters now support configurable source locations. Use `Format::with_source_location` instead."
)]
pub fn with_source_location(self, display_location: bool) -> Self {
Self {
display_location,
..self
}
}
}
impl FormatEvent for Format
where
C: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
T: FormatTime,
{
fn format_event(
&self,
ctx: &FmtContext<'_, C, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> fmt::Result {
#[cfg(feature = "tracing-log")]
let normalized_meta = event.normalized_metadata();
#[cfg(feature = "tracing-log")]
let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
#[cfg(not(feature = "tracing-log"))]
let meta = event.metadata();
write!(&mut writer, " ")?;
// if the `Format` struct *also* has an ANSI color configuration,
// override the writer...the API for configuring ANSI color codes on the
// `Format` struct is deprecated, but we still need to honor those
// configurations.
if let Some(ansi) = self.ansi {
writer = writer.with_ansi(ansi);
}
self.format_timestamp(&mut writer)?;
let style = if self.display_level && writer.has_ansi_escapes() {
Pretty::style_for(meta.level())
} else {
Style::new()
};
if self.display_level {
write!(
writer,
"{} ",
super::FmtLevel::new(meta.level(), writer.has_ansi_escapes())
)?;
}
if self.display_target {
let target_style = if writer.has_ansi_escapes() {
style.bold()
} else {
style
};
write!(
writer,
"{}{}{}:",
target_style.prefix(),
meta.target(),
target_style.infix(style)
)?;
}
let line_number = if self.display_line_number {
meta.line()
} else {
None
};
// If the file name is disabled, format the line number right after the
// target. Otherwise, if we also display the file, it'll go on a
// separate line.
if let (Some(line_number), false, true) = (
line_number,
self.display_filename,
self.format.display_location,
) {
write!(
writer,
"{}{}{}:",
style.prefix(),
line_number,
style.infix(style)
)?;
}
writer.write_char(' ')?;
let mut v = PrettyVisitor::new(writer.by_ref(), true).with_style(style);
event.record(&mut v);
v.finish()?;
writer.write_char('\n')?;
let dimmed = if writer.has_ansi_escapes() {
Style::new().dimmed().italic()
} else {
Style::new()
};
let thread = self.display_thread_name || self.display_thread_id;
if let (Some(file), true, true) = (
meta.file(),
self.format.display_location,
self.display_filename,
) {
write!(writer, " {} {}", dimmed.paint("at"), file,)?;
if let Some(line) = line_number {
write!(writer, ":{}", line)?;
}
writer.write_char(if thread { ' ' } else { '\n' })?;
} else if thread {
write!(writer, " ")?;
};
if thread {
write!(writer, "{} ", dimmed.paint("on"))?;
let thread = std::thread::current();
if self.display_thread_name {
if let Some(name) = thread.name() {
write!(writer, "{}", name)?;
if self.display_thread_id {
writer.write_char(' ')?;
}
}
}
if self.display_thread_id {
write!(writer, "{:?}", thread.id())?;
}
writer.write_char('\n')?;
}
let bold = writer.bold();
let span = event
.parent()
.and_then(|id| ctx.span(id))
.or_else(|| ctx.lookup_current());
let scope = span.into_iter().flat_map(|span| span.scope());
for span in scope {
let meta = span.metadata();
if self.display_target {
write!(
writer,
" {} {}::{}",
dimmed.paint("in"),
meta.target(),
bold.paint(meta.name()),
)?;
} else {
write!(
writer,
" {} {}",
dimmed.paint("in"),
bold.paint(meta.name()),
)?;
}
let ext = span.extensions();
let fields = &ext
.get::>()
.expect("Unable to find FormattedFields in extensions; this is a bug");
if !fields.is_empty() {
write!(writer, " {} {}", dimmed.paint("with"), fields)?;
}
writer.write_char('\n')?;
}
writer.write_char('\n')
}
}
impl<'writer> FormatFields<'writer> for Pretty {
fn format_fields(&self, writer: Writer<'writer>, fields: R) -> fmt::Result {
let mut v = PrettyVisitor::new(writer, true);
fields.record(&mut v);
v.finish()
}
fn add_fields(
&self,
current: &'writer mut FormattedFields,
fields: &span::Record<'_>,
) -> fmt::Result {
let empty = current.is_empty();
let writer = current.as_writer();
let mut v = PrettyVisitor::new(writer, empty);
fields.record(&mut v);
v.finish()
}
}
// === impl PrettyFields ===
impl Default for PrettyFields {
fn default() -> Self {
Self::new()
}
}
impl PrettyFields {
/// Returns a new default [`PrettyFields`] implementation.
pub fn new() -> Self {
// By default, don't override the `Writer`'s ANSI colors
// configuration. We'll only do this if the user calls the
// deprecated `PrettyFields::with_ansi` method.
Self { ansi: None }
}
/// Enable ANSI encoding for formatted fields.
#[deprecated(
since = "0.3.3",
note = "Use `fmt::Subscriber::with_ansi` or `fmt::Layer::with_ansi` instead."
)]
pub fn with_ansi(self, ansi: bool) -> Self {
Self {
ansi: Some(ansi),
..self
}
}
}
impl<'a> MakeVisitor> for PrettyFields {
type Visitor = PrettyVisitor<'a>;
#[inline]
fn make_visitor(&self, mut target: Writer<'a>) -> Self::Visitor {
if let Some(ansi) = self.ansi {
target = target.with_ansi(ansi);
}
PrettyVisitor::new(target, true)
}
}
// === impl PrettyVisitor ===
impl<'a> PrettyVisitor<'a> {
/// Returns a new default visitor that formats to the provided `writer`.
///
/// # Arguments
/// - `writer`: the writer to format to.
/// - `is_empty`: whether or not any fields have been previously written to
/// that writer.
pub fn new(writer: Writer<'a>, is_empty: bool) -> Self {
Self {
writer,
is_empty,
style: Style::default(),
result: Ok(()),
}
}
pub(crate) fn with_style(self, style: Style) -> Self {
Self { style, ..self }
}
fn write_padded(&mut self, value: &impl fmt::Debug) {
let padding = if self.is_empty {
self.is_empty = false;
""
} else {
", "
};
self.result = write!(self.writer, "{}{:?}", padding, value);
}
fn bold(&self) -> Style {
if self.writer.has_ansi_escapes() {
self.style.bold()
} else {
Style::new()
}
}
}
impl<'a> field::Visit for PrettyVisitor<'a> {
fn record_str(&mut self, field: &Field, value: &str) {
if self.result.is_err() {
return;
}
if field.name() == "message" {
self.record_debug(field, &format_args!("{}", value))
} else {
self.record_debug(field, &value)
}
}
fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
if let Some(source) = value.source() {
let bold = self.bold();
self.record_debug(
field,
&format_args!(
"{}, {}{}.sources{}: {}",
value,
bold.prefix(),
field,
bold.infix(self.style),
ErrorSourceList(source),
),
)
} else {
self.record_debug(field, &format_args!("{}", value))
}
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
if self.result.is_err() {
return;
}
let bold = self.bold();
match field.name() {
"message" => self.write_padded(&format_args!("{}{:?}", self.style.prefix(), value,)),
// Skip fields that are actually log metadata that have already been handled
#[cfg(feature = "tracing-log")]
name if name.starts_with("log.") => self.result = Ok(()),
name if name.starts_with("r#") => self.write_padded(&format_args!(
"{}{}{}: {:?}",
bold.prefix(),
&name[2..],
bold.infix(self.style),
value
)),
name => self.write_padded(&format_args!(
"{}{}{}: {:?}",
bold.prefix(),
name,
bold.infix(self.style),
value
)),
};
}
}
impl<'a> VisitOutput for PrettyVisitor<'a> {
fn finish(mut self) -> fmt::Result {
write!(&mut self.writer, "{}", self.style.suffix())?;
self.result
}
}
impl<'a> VisitFmt for PrettyVisitor<'a> {
fn writer(&mut self) -> &mut dyn fmt::Write {
&mut self.writer
}
}