#[deny(rustdoc::broken_intra_doc_links)]
mod format;
#[cfg(feature = "ansi")]
mod nu_ansi_term {
pub use ::nu_ansi_term::*;
}
#[cfg(not(feature = "ansi"))]
mod nu_ansi_term {
#[derive(Copy, Clone)]
pub struct Style;
impl Style {
pub fn new() -> Self {
Style
}
pub fn bold(&self) -> Self {
Style
}
pub fn prefix(&self) -> &'static str {
""
}
pub fn infix(&self, _: Style) -> &'static str {
""
}
pub fn suffix(&self) -> &'static str {
""
}
}
}
use crate::nu_ansi_term::Style;
use format::FmtLevel;
pub use format::{LocalTime, UtcTime};
use std::fmt;
use tracing::{
field::{Field, Visit},
Subscriber,
};
#[cfg(feature = "tracing-log")]
use tracing_log::NormalizeEvent;
use tracing_subscriber::{
field::{MakeVisitor, VisitFmt, VisitOutput},
fmt::{
format::Writer, time::FormatTime, FmtContext, FormatEvent, FormatFields, FormattedFields,
},
registry::LookupSpan,
};
use crate::format::{FormatProcessData, FormatSpanFields};
pub struct Glog<T = UtcTime> {
timer: T,
with_span_context: bool,
with_thread_names: bool,
with_target: bool,
}
impl<T> Glog<T> {
pub fn with_timer<T2>(self, timer: T2) -> Glog<T2>
where
T2: FormatTime,
{
Glog {
timer,
with_thread_names: self.with_thread_names,
with_target: self.with_target,
with_span_context: self.with_span_context,
}
}
pub fn with_thread_names(self, with_thread_names: bool) -> Glog<T> {
Glog {
with_thread_names,
..self
}
}
pub fn with_target(self, with_target: bool) -> Glog<T> {
Glog {
with_target,
..self
}
}
pub fn with_span_context(self, with_span_context: bool) -> Glog<T> {
Glog {
with_span_context,
..self
}
}
}
impl Default for Glog<UtcTime> {
fn default() -> Self {
Glog {
timer: UtcTime::default(),
with_thread_names: false,
with_target: false,
with_span_context: true,
}
}
}
impl<S, N, T> FormatEvent<S, N> for Glog<T>
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
T: FormatTime,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &tracing::Event<'_>,
) -> fmt::Result {
let level = *event.metadata().level();
let level = FmtLevel::format_level(level, writer.has_ansi_escapes());
write!(writer, "{}", level)?;
self.timer.format_time(&mut writer)?;
let pid = get_pid();
let thread = std::thread::current();
let thread_name = thread.name();
#[cfg(feature = "tracing-log")]
let normalized_meta = event.normalized_metadata();
#[cfg(feature = "tracing-log")]
let metadata = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
#[cfg(not(feature = "tracing-log"))]
let metadata = event.metadata();
let data = FormatProcessData {
pid,
thread_name,
with_thread_names: self.with_thread_names,
metadata,
with_target: self.with_target,
#[cfg(feature = "ansi")]
ansi: writer.has_ansi_escapes(),
};
write!(writer, "{}] ", data)?;
if self.with_span_context {
let leaf = ctx.lookup_current();
if let Some(leaf) = leaf {
write!(writer, "[")?;
let mut iter = leaf.scope().from_root();
let mut span = iter.next().expect(
"Unable to get the next item in the iterator; this should not be possible.",
);
loop {
let ext = span.extensions();
let fields = &ext
.get::<FormattedFields<N>>()
.expect("will never be `None`");
let fields = if !fields.is_empty() {
Some(fields.as_str())
} else {
None
};
let fields = FormatSpanFields::format_fields(
span.name(),
fields,
writer.has_ansi_escapes(),
);
write!(writer, "{}", fields)?;
drop(ext);
match iter.next() {
Some(next) => {
write!(writer, ", ")?;
span = next;
}
None => break,
}
}
write!(writer, "] ")?;
}
}
ctx.field_format().format_fields(writer.by_ref(), event)?;
writeln!(writer)
}
}
#[derive(Default)]
pub struct GlogFields;
impl<'a> MakeVisitor<Writer<'a>> for GlogFields {
type Visitor = GlogVisitor<'a>;
#[inline]
fn make_visitor(&self, target: Writer<'a>) -> Self::Visitor {
GlogVisitor::new(target)
}
}
#[doc(hidden)]
pub struct GlogVisitor<'a> {
writer: Writer<'a>,
is_empty: bool,
style: Style,
result: fmt::Result,
}
impl<'a> GlogVisitor<'a> {
fn new(writer: Writer<'a>) -> Self {
Self {
writer,
is_empty: true,
style: Style::new(),
result: Ok(()),
}
}
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> Visit for GlogVisitor<'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() {
self.record_debug(
field,
&format_args!("{}, {}.sources: {}", value, field, 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,)),
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<fmt::Result> for GlogVisitor<'a> {
fn finish(mut self) -> fmt::Result {
write!(&mut self.writer, "{}", self.style.suffix())?;
self.result
}
}
impl<'a> VisitFmt for GlogVisitor<'a> {
fn writer(&mut self) -> &mut dyn fmt::Write {
&mut self.writer
}
}
struct ErrorSourceList<'a>(&'a (dyn std::error::Error + 'static));
impl<'a> fmt::Display for ErrorSourceList<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut list = f.debug_list();
let mut curr = Some(self.0);
while let Some(curr_err) = curr {
list.entry(&format_args!("{}", curr_err));
curr = curr_err.source();
}
list.finish()
}
}
#[inline(always)]
fn get_pid() -> u32 {
std::process::id()
}