#![warn(missing_docs)]
#![warn(clippy::all)]
use std::fmt;
use jiff::{Timestamp, Zoned, tz::TimeZone};
use nu_ansi_term::{Color, Style};
use tracing_core::{Event, Level, Subscriber, field::Field};
use tracing_log::NormalizeEvent;
use tracing_subscriber::{
field::{MakeVisitor, Visit, VisitFmt, VisitOutput},
fmt::{FmtContext, FormatEvent, FormatFields, FormattedFields, format::Writer},
registry::LookupSpan,
};
#[cfg(feature = "re-exports")]
pub use tracing;
#[cfg(feature = "re-exports")]
pub mod macros {
#[doc(no_inline)]
pub use tracing::{
debug, debug_span, enabled, error, error_span, event, event_enabled, info, info_span, span,
span_enabled, trace, trace_span, warn, warn_span,
};
}
mod config;
pub use config::{ColorMode, Config, Output};
trait WriterExt: fmt::Write {
fn enable_ansi(&self) -> bool;
fn write_style<S, T>(&mut self, style: S, value: T) -> fmt::Result
where
S: Into<Style>,
T: fmt::Display,
{
if self.enable_ansi() {
let style = style.into();
write!(self, "{}{}{}", style.prefix(), value, style.suffix())
} else {
write!(self, "{}", value)
}
}
}
impl WriterExt for Writer<'_> {
#[inline]
fn enable_ansi(&self) -> bool {
self.has_ansi_escapes()
}
}
macro_rules! write_style {
($writer:expr, $style:expr, $($arg:tt)*) => {
$writer.write_style($style, format_args!($($arg)*))
};
}
#[derive(Clone)]
pub struct FieldFormatter {
_private: (),
}
impl FieldFormatter {
pub fn new() -> Self {
Self { _private: () }
}
}
impl Default for FieldFormatter {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for FieldFormatter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.pad("FieldFormatter")
}
}
impl<'a> MakeVisitor<Writer<'a>> for FieldFormatter {
type Visitor = FieldVisitor<'a>;
fn make_visitor(&self, target: Writer<'a>) -> Self::Visitor {
FieldVisitor::new(target)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FieldType {
None,
Message,
Other,
}
#[derive(Debug)]
pub struct FieldVisitor<'a> {
writer: Writer<'a>,
result: fmt::Result,
last: FieldType,
}
impl<'a> FieldVisitor<'a> {
pub fn new(writer: Writer<'a>) -> Self {
Self { writer, result: Ok(()), last: FieldType::None }
}
fn pad_for_message(&self) -> &'static str {
match self.last {
FieldType::None => "",
FieldType::Message | FieldType::Other => " ",
}
}
fn pad_for_other(&self) -> &'static str {
match self.last {
FieldType::Message => " ",
FieldType::None | FieldType::Other => "",
}
}
}
impl Visit for FieldVisitor<'_> {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
if self.result.is_err() {
return;
}
let name = field.name();
if name.starts_with("log.") {
return;
}
self.result = if name == "message" {
let pad = self.pad_for_message();
self.last = FieldType::Message;
write!(self.writer, "{pad}{value:?}")
} else {
let pad = self.pad_for_other();
self.last = FieldType::Other;
write_style!(self.writer, Style::default().dimmed(), "{pad}[{name}={value:?}]")
};
}
fn record_str(&mut self, field: &Field, value: &str) {
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 self.result.is_err() {
return;
}
let name = field.name();
if name.starts_with("log.") {
return;
}
let pad = self.pad_for_other();
self.last = FieldType::Other;
self.result = write_style!(self.writer, Color::Red.dimmed(), "{pad}[{name}={value}]");
}
}
impl VisitOutput<fmt::Result> for FieldVisitor<'_> {
fn finish(self) -> fmt::Result {
self.result
}
}
impl VisitFmt for FieldVisitor<'_> {
fn writer(&mut self) -> &mut dyn fmt::Write {
&mut self.writer
}
}
#[derive(Clone)]
pub struct TimeFormat {
inner: InnerTimeFormat,
}
#[derive(Clone)]
enum InnerTimeFormat {
None,
Local(Option<Box<str>>),
Utc(Option<Box<str>>),
}
impl Default for TimeFormat {
fn default() -> Self {
Self::local()
}
}
impl fmt::Debug for TimeFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.inner {
InnerTimeFormat::None => f.write_str("TimeFormat::None"),
InnerTimeFormat::Local(format) => write!(f, "TimeFormat::Local({format:?})"),
InnerTimeFormat::Utc(format) => write!(f, "TimeFormat::Utc({format:?})"),
}
}
}
impl TimeFormat {
pub const LOCAL_FORMAT: &'static str = "[%Y-%m-%dT%H:%M:%S%z]";
pub const UTC_FORMAT: &'static str = "[%Y-%m-%dT%H:%M:%SZ]";
pub const fn none() -> Self {
Self { inner: InnerTimeFormat::None }
}
pub const fn local() -> Self {
Self { inner: InnerTimeFormat::Local(None) }
}
pub const fn utc() -> Self {
Self { inner: InnerTimeFormat::Utc(None) }
}
pub fn local_custom(format: impl Into<String>) -> Self {
let format = format.into();
#[cfg(debug_assertions)]
{
let zoned = Zoned::new(Timestamp::UNIX_EPOCH, TimeZone::UTC);
let res = jiff::fmt::strtime::format(format.as_bytes(), &zoned);
if let Err(err) = res {
panic!("Unable to use custom TimeFormat '{format}': {err}");
}
}
Self { inner: InnerTimeFormat::Local(Some(format.into_boxed_str())) }
}
pub fn utc_custom(format: impl Into<String>) -> Self {
let format = format.into();
#[cfg(debug_assertions)]
{
let res = jiff::fmt::strtime::format(format.as_bytes(), Timestamp::UNIX_EPOCH);
if let Err(err) = res {
panic!("Unable to use custom TimeFormat '{format}': {err}");
}
}
Self { inner: InnerTimeFormat::Utc(Some(format.into_boxed_str())) }
}
pub fn render(&self, ts: Timestamp) -> impl fmt::Display + '_ {
TimeDisplay(self, ts)
}
pub fn render_now(&self) -> impl fmt::Display + '_ {
self.render(Timestamp::now())
}
fn is_none(&self) -> bool {
matches!(self.inner, InnerTimeFormat::None)
}
}
struct TimeDisplay<'a>(&'a TimeFormat, Timestamp);
impl fmt::Display for TimeDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.0.inner {
InnerTimeFormat::None => Ok(()),
InnerTimeFormat::Local(format) => {
let format = format.as_deref().unwrap_or(TimeFormat::LOCAL_FORMAT);
let zoned = Zoned::new(self.1, TimeZone::system());
let disp = zoned.strftime(format.as_bytes());
fmt::Display::fmt(&disp, f)
}
InnerTimeFormat::Utc(format) => {
let format = format.as_deref().unwrap_or(TimeFormat::UTC_FORMAT);
let disp = self.1.strftime(format.as_bytes());
fmt::Display::fmt(&disp, f)
}
}
}
}
#[derive(Debug, Clone)]
pub struct EventFormatter {
time_format: TimeFormat,
display_target: bool,
display_scope: bool,
}
impl EventFormatter {
pub fn new() -> Self {
Self { time_format: Default::default(), display_target: true, display_scope: true }
}
pub fn with_timestamp(self, time_format: TimeFormat) -> Self {
Self { time_format, ..self }
}
pub fn with_target(self, display_target: bool) -> Self {
Self { display_target, ..self }
}
pub fn with_scope(self, display_scope: bool) -> Self {
Self { display_scope, ..self }
}
}
impl Default for EventFormatter {
fn default() -> Self {
Self::new()
}
}
impl<S, N> FormatEvent<S, N> for EventFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> fmt::Result {
let norm_meta = event.normalized_metadata();
let meta = norm_meta.as_ref().unwrap_or_else(|| event.metadata());
if !self.time_format.is_none() {
write_style!(writer, Style::default().dimmed(), "{} ", self.time_format.render_now(),)?;
}
let level = *meta.level();
let level_style = match level {
Level::TRACE => Color::Purple,
Level::DEBUG => Color::Blue,
Level::INFO => Color::Green,
Level::WARN => Color::Yellow,
Level::ERROR => Color::Red,
};
write_style!(writer, level_style, "{level:>5} ")?;
let maybe_scope = if self.display_scope { ctx.event_scope() } else { None };
if let Some(scope) = maybe_scope {
let mut seen = false;
for span in scope.from_root() {
writer.write_style(Color::Cyan.dimmed(), span.metadata().name())?;
seen = true;
if let Some(fields) = span.extensions().get::<FormattedFields<N>>() {
if !fields.is_empty() {
write!(writer, "{}:", fields)?;
}
}
}
if seen {
writer.write_char(' ')?;
}
}
if self.display_target {
write_style!(writer, Color::Blue.dimmed(), "{}", meta.target())?;
writer.write_str(": ")?;
}
ctx.format_fields(writer.by_ref(), event)?;
writeln!(writer)
}
}