blob: a7d95a3f8e333ea29fcbc04cdc5579b81391ecf8 [file] [log] [blame]
use nu_ansi_term::Color;
use std::{
fmt::{self, Write as _},
io,
};
use tracing_core::{
field::{Field, Visit},
span, Level,
};
pub(crate) const LINE_VERT: &str = "│";
const LINE_HORIZ: &str = "─";
pub(crate) const LINE_BRANCH: &str = "├";
pub(crate) const LINE_CLOSE: &str = "┘";
pub(crate) const LINE_OPEN: &str = "┐";
#[derive(Debug, Copy, Clone)]
pub(crate) enum SpanMode {
/// Executed on the parent before entering a child span
PreOpen,
Open {
verbose: bool,
},
Close {
verbose: bool,
},
/// A span has been entered but another *different* span has been entered in the meantime.
Retrace {
verbose: bool,
},
PostClose,
Event,
}
#[derive(Debug)]
pub struct Config {
/// Whether to use colors.
pub ansi: bool,
/// Whether an ascii art tree is used or (if false) whether to just use whitespace indent
pub indent_lines: bool,
/// The amount of chars to indent.
pub indent_amount: usize,
/// Whether to show the module paths.
pub targets: bool,
/// Whether to show thread ids.
pub render_thread_ids: bool,
/// Whether to show thread names.
pub render_thread_names: bool,
/// Specifies after how many indentation levels we will wrap back around to zero
pub wraparound: usize,
/// Whether to print the current span before activating a new one
pub verbose_entry: bool,
/// Whether to print the current span before exiting it.
pub verbose_exit: bool,
/// Print the path leading up to a span if a different span was entered concurrently
pub span_retrace: bool,
/// Whether to print squiggly brackets (`{}`) around the list of fields in a span.
pub bracketed_fields: bool,
/// Defer printing a span until an event is generated inside of it
pub deferred_spans: bool,
/// Print a label of the span mode (open/close etc).
pub span_modes: bool,
/// Whether to print the time with higher precision.
pub higher_precision: bool,
}
impl Config {
pub fn with_ansi(self, ansi: bool) -> Self {
Self { ansi, ..self }
}
pub fn with_indent_lines(self, indent_lines: bool) -> Self {
Self {
indent_lines,
..self
}
}
pub fn with_targets(self, targets: bool) -> Self {
Self { targets, ..self }
}
pub fn with_thread_ids(self, render_thread_ids: bool) -> Self {
Self {
render_thread_ids,
..self
}
}
pub fn with_thread_names(self, render_thread_names: bool) -> Self {
Self {
render_thread_names,
..self
}
}
pub fn with_wraparound(self, wraparound: usize) -> Self {
Self { wraparound, ..self }
}
pub fn with_verbose_entry(self, verbose_entry: bool) -> Self {
Self {
verbose_entry,
..self
}
}
pub fn with_verbose_exit(self, verbose_exit: bool) -> Self {
Self {
verbose_exit,
..self
}
}
pub fn with_span_retrace(self, enabled: bool) -> Self {
Self {
span_retrace: enabled,
..self
}
}
pub fn with_deferred_spans(self, enable: bool) -> Self {
Self {
deferred_spans: enable,
..self
}
}
pub fn with_span_modes(self, enable: bool) -> Self {
Self {
span_modes: enable,
..self
}
}
pub fn with_bracketed_fields(self, bracketed_fields: bool) -> Self {
Self {
bracketed_fields,
..self
}
}
pub fn with_higher_precision(self, higher_precision: bool) -> Self {
Self {
higher_precision,
..self
}
}
pub(crate) fn prefix(&self) -> String {
let mut buf = String::new();
if self.render_thread_ids {
write!(buf, "{:?}", std::thread::current().id()).unwrap();
if buf.ends_with(')') {
buf.truncate(buf.len() - 1);
}
if buf.starts_with("ThreadId(") {
buf.drain(0.."ThreadId(".len());
}
}
if self.render_thread_names {
if let Some(name) = std::thread::current().name() {
if self.render_thread_ids {
buf.push(':');
}
buf.push_str(name);
}
}
buf
}
}
impl Default for Config {
fn default() -> Self {
Self {
ansi: true,
indent_lines: false,
indent_amount: 2,
targets: false,
render_thread_ids: false,
render_thread_names: false,
wraparound: usize::max_value(),
verbose_entry: false,
verbose_exit: false,
span_retrace: false,
bracketed_fields: false,
deferred_spans: false,
span_modes: false,
higher_precision: false,
}
}
}
#[derive(Debug)]
pub struct Buffers {
pub current_buf: String,
pub indent_buf: String,
/// The last seen span of this layer
///
/// This serves to serialize spans as two events can be generated in different spans
/// without the spans entering and exiting beforehand. This happens for multithreaded code
/// and instrumented futures
pub current_span: Option<span::Id>,
}
impl Buffers {
pub fn new() -> Self {
Self {
current_buf: String::new(),
indent_buf: String::new(),
current_span: None,
}
}
pub fn flush_current_buf(&mut self, mut writer: impl io::Write) {
write!(writer, "{}", &self.current_buf).unwrap();
self.current_buf.clear();
}
pub fn flush_indent_buf(&mut self) {
self.current_buf.push_str(&self.indent_buf);
self.indent_buf.clear();
}
pub(crate) fn indent_current(&mut self, indent: usize, config: &Config, style: SpanMode) {
let prefix = config.prefix();
// Render something when wraparound occurs so the user is aware of it
if config.indent_lines {
self.current_buf.push('\n');
match style {
SpanMode::Close { .. } | SpanMode::PostClose => {
if indent > 0 && (indent + 1) % config.wraparound == 0 {
self.indent_buf.push_str(&prefix);
for _ in 0..(indent % config.wraparound * config.indent_amount) {
self.indent_buf.push_str(LINE_HORIZ);
}
self.indent_buf.push_str(LINE_OPEN);
self.indent_buf.push('\n');
}
}
_ => {}
}
}
indent_block(
&mut self.current_buf,
&mut self.indent_buf,
indent % config.wraparound,
config.indent_amount,
config.indent_lines,
&prefix,
style,
);
self.current_buf.clear();
self.flush_indent_buf();
// Render something when wraparound occurs so the user is aware of it
if config.indent_lines {
match style {
SpanMode::PreOpen { .. } | SpanMode::Open { .. } => {
if indent > 0 && (indent + 1) % config.wraparound == 0 {
self.current_buf.push_str(&prefix);
for _ in 0..(indent % config.wraparound * config.indent_amount) {
self.current_buf.push_str(LINE_HORIZ);
}
self.current_buf.push_str(LINE_CLOSE);
self.current_buf.push('\n');
}
}
_ => {}
}
}
}
}
pub struct FmtEvent<'a> {
pub bufs: &'a mut Buffers,
pub comma: bool,
}
impl<'a> Visit for FmtEvent<'a> {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
let buf = &mut self.bufs.current_buf;
let comma = if self.comma { "," } else { "" };
match field.name() {
"message" => {
write!(buf, "{} {:?}", comma, value).unwrap();
self.comma = true;
}
// Skip fields that are actually log metadata that have already been handled
#[cfg(feature = "tracing-log")]
name if name.starts_with("log.") => {}
name => {
write!(buf, "{} {}={:?}", comma, name, value).unwrap();
self.comma = true;
}
}
}
}
pub struct ColorLevel<'a>(pub &'a Level);
impl<'a> fmt::Display for ColorLevel<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self.0 {
Level::TRACE => Color::Purple.bold().paint("TRACE"),
Level::DEBUG => Color::Blue.bold().paint("DEBUG"),
Level::INFO => Color::Green.bold().paint(" INFO"),
Level::WARN => Color::Rgb(252, 234, 160).bold().paint(" WARN"), // orange
Level::ERROR => Color::Red.bold().paint("ERROR"),
}
.fmt(f)
}
}
pub(crate) fn write_span_mode(buf: &mut String, style: SpanMode) {
match style {
SpanMode::Open { verbose: true } => buf.push_str("open(v)"),
SpanMode::Open { verbose: false } => buf.push_str("open"),
SpanMode::Retrace { verbose: false } => buf.push_str("retrace"),
SpanMode::Retrace { verbose: true } => buf.push_str("retrace(v)"),
SpanMode::Close { verbose: true } => buf.push_str("close(v)"),
SpanMode::Close { verbose: false } => buf.push_str("close"),
SpanMode::PreOpen => buf.push_str("pre_open"),
SpanMode::PostClose => buf.push_str("post_close"),
SpanMode::Event => buf.push_str("event"),
}
buf.push_str(": ")
}
fn indent_block_with_lines(
lines: &[&str],
buf: &mut String,
indent: usize,
// width of one level of indent
indent_amount: usize,
prefix: &str,
style: SpanMode,
) {
let indent_spaces = indent * indent_amount;
if lines.is_empty() {
return;
} else if indent_spaces == 0 {
for line in lines {
buf.push_str(prefix);
// The first indent is special, we only need to print open/close and nothing else
if indent == 0 {
match style {
SpanMode::Open { .. } => buf.push_str(LINE_OPEN),
SpanMode::Retrace { .. } => buf.push_str(LINE_OPEN),
SpanMode::Close { .. } => buf.push_str(LINE_CLOSE),
SpanMode::PreOpen { .. } | SpanMode::PostClose => {}
SpanMode::Event => {}
}
}
buf.push_str(line);
buf.push('\n');
}
return;
}
let mut s = String::with_capacity(indent_spaces + prefix.len());
s.push_str(prefix);
// instead of using all spaces to indent, draw a vertical line at every indent level
// up until the last indent
for i in 0..(indent_spaces - indent_amount) {
if i % indent_amount == 0 {
s.push_str(LINE_VERT);
} else {
s.push(' ');
}
}
// draw branch
buf.push_str(&s);
match style {
SpanMode::PreOpen => {
buf.push_str(LINE_BRANCH);
for _ in 1..(indent_amount / 2) {
buf.push_str(LINE_HORIZ);
}
buf.push_str(LINE_OPEN);
}
SpanMode::Open { verbose: false } | SpanMode::Retrace { verbose: false } => {
buf.push_str(LINE_BRANCH);
for _ in 1..indent_amount {
buf.push_str(LINE_HORIZ);
}
buf.push_str(LINE_OPEN);
}
SpanMode::Open { verbose: true } | SpanMode::Retrace { verbose: true } => {
buf.push_str(LINE_VERT);
for _ in 1..(indent_amount / 2) {
buf.push(' ');
}
// We don't have the space for fancy rendering at single space indent.
if indent_amount > 1 {
buf.push('└');
}
for _ in (indent_amount / 2)..(indent_amount - 1) {
buf.push_str(LINE_HORIZ);
}
// We don't have the space for fancy rendering at single space indent.
if indent_amount > 1 {
buf.push_str(LINE_OPEN);
} else {
buf.push_str(LINE_VERT);
}
}
SpanMode::Close { verbose: false } => {
buf.push_str(LINE_BRANCH);
for _ in 1..indent_amount {
buf.push_str(LINE_HORIZ);
}
buf.push_str(LINE_CLOSE);
}
SpanMode::Close { verbose: true } => {
buf.push_str(LINE_VERT);
for _ in 1..(indent_amount / 2) {
buf.push(' ');
}
// We don't have the space for fancy rendering at single space indent.
if indent_amount > 1 {
buf.push('┌');
}
for _ in (indent_amount / 2)..(indent_amount - 1) {
buf.push_str(LINE_HORIZ);
}
// We don't have the space for fancy rendering at single space indent.
if indent_amount > 1 {
buf.push_str(LINE_CLOSE);
} else {
buf.push_str(LINE_VERT);
}
}
SpanMode::PostClose => {
buf.push_str(LINE_BRANCH);
for _ in 1..(indent_amount / 2) {
buf.push_str(LINE_HORIZ);
}
buf.push_str(LINE_CLOSE);
}
SpanMode::Event => {
buf.push_str(LINE_BRANCH);
// add `indent_amount - 1` horizontal lines before the span/event
for _ in 0..(indent_amount - 1) {
buf.push_str(LINE_HORIZ);
}
}
}
buf.push_str(lines[0]);
buf.push('\n');
// add the rest of the indentation, since we don't want to draw horizontal lines
// for subsequent lines
for i in 0..indent_amount {
if i % indent_amount == 0 {
s.push_str(LINE_VERT);
} else {
s.push(' ');
}
}
// add all of the actual content, with each line preceded by the indent string
for line in &lines[1..] {
buf.push_str(&s);
buf.push_str(line);
buf.push('\n');
}
}
fn indent_block(
block: &mut String,
buf: &mut String,
mut indent: usize,
indent_amount: usize,
indent_lines: bool,
prefix: &str,
style: SpanMode,
) {
let lines: Vec<&str> = block.lines().collect();
let indent_spaces = indent * indent_amount;
buf.reserve(block.len() + (lines.len() * indent_spaces));
// The PreOpen and PostClose need to match up with the indent of the entered child span one more indent
// deep
match style {
SpanMode::PreOpen | SpanMode::PostClose => {
indent += 1;
}
_ => (),
}
if indent_lines {
indent_block_with_lines(&lines, buf, indent, indent_amount, prefix, style);
} else {
let indent_str = String::from(" ").repeat(indent_spaces);
for line in lines {
buf.push_str(prefix);
buf.push(' ');
buf.push_str(&indent_str);
buf.push_str(line);
buf.push('\n');
}
}
}