1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
//! Structures to represent an event created by [`tracing`](https://docs.rs/tracing).
use anyhow::Context;
use anyhow::Result;
use itertools::Itertools;
use nu_ansi_term::Color;
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::fmt::{Display, Formatter, Result as FmtResult};
/// Represents an event created by `tracing`.
///
/// It is not possible to construct one directly from a `tracing` event.
/// Instead its expected that they are returned by one of the methods on [`crate::BinProcess`] which retrives them by parsing `tracing`'s JSON output.
#[derive(serde::Deserialize, Debug, Clone, PartialEq)]
pub struct Event {
/// The timestamp of the event.
pub timestamp: String,
/// The level of the event.
pub level: Level,
/// The target of the event, this is usually the module name of the code that triggered the event.
pub target: String,
/// Contains the message and other fields included in the event.
pub fields: Fields,
/// The last span that was entered before the event was triggered.
#[serde(default)]
pub span: HashMap<String, JsonValue>,
/// Every span that was active while the event was triggered.
#[serde(default)]
pub spans: Vec<HashMap<String, JsonValue>>,
}
/// The level of the `Event`.
#[derive(serde::Deserialize, Debug, Clone, PartialEq)]
pub enum Level {
#[serde(rename = "ERROR")]
Error,
#[serde(rename = "WARN")]
Warn,
#[serde(rename = "INFO")]
Info,
#[serde(rename = "DEBUG")]
Debug,
#[serde(rename = "TRACE")]
Trace,
}
impl Display for Level {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Level::Error => write!(f, "{}", Color::Red.paint("ERROR")),
Level::Warn => write!(f, " {}", Color::Yellow.paint("WARN")),
Level::Info => write!(f, " {}", Color::Green.paint("INFO")),
Level::Debug => write!(f, " {}", Color::Blue.paint("DEBUG")),
Level::Trace => write!(f, " {}", Color::Purple.paint("TRACE")),
}
}
}
/// Contains the message and other fields included in the event.
#[derive(serde::Deserialize, Debug, Clone, PartialEq)]
pub struct Fields {
/// The message of the event.
/// Some events dont have a message in which case this is an empty `String`.
#[serde(default)]
pub message: String,
/// All fields other than the message.
/// For an event created by: `tracing::info!("message", some_field=4)`
/// This would contain the `HashMap`: `{"some_field", JsonValue::Number(4)}`.
#[serde(flatten)]
pub fields: HashMap<String, JsonValue>,
}
impl Display for Event {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(
f,
"{}",
Color::Default.dimmed().paint(
// If timestamp in expected rfc3339 format then strip the date from the datetime.
// The date portion isnt useful in integration tests.
if chrono::DateTime::parse_from_rfc3339(&self.timestamp).is_ok() {
// Now that we have confirmed it as an rfc3339 date we can take the easy path of just stripping the date prefix.
&self.timestamp[11..]
} else {
&self.timestamp
}
)
)?;
if let Some(backtrace) = self.fields.fields.get("panic.backtrace") {
// Special case panics.
// Panics usually get reasonable formatting locally and we dont want to regress on that just because we had to stuff them through tracing
writeln!(
f,
" {} {}",
Color::Red.reverse().paint("PANIC"),
self.fields.message,
)?;
match backtrace {
JsonValue::String(backtrace) => {
for line in backtrace.lines() {
// we can have a little color as a treat
if line.trim().starts_with("at ") {
writeln!(f, "{}", Color::Default.dimmed().paint(line))?;
} else {
writeln!(f, "{}", line)?;
}
}
}
backtrace => write!(f, "{backtrace}")?,
}
} else {
// Regular old formatting, matches the formatting used by the default tracing subscriber
write!(f, " {} ", self.level)?;
if !self.spans.is_empty() {
for span in &self.spans {
let name = span.get("name").unwrap().as_str().unwrap();
write!(f, "{}", Color::Default.bold().paint(name))?;
write!(f, "{}", Color::Default.bold().paint("{"))?;
let mut first = true;
for (key, value) in span.iter().sorted_by_key(|(key, _)| <&String>::clone(key))
{
if key != "name" {
if !first {
write!(f, " ")?;
}
first = false;
write!(f, "{}", key)?;
write!(f, "{}", Color::Default.dimmed().paint("="))?;
write!(f, "{}", value)?;
}
}
write!(f, "{}", Color::Default.bold().paint("}"))?;
write!(f, "{}", Color::Default.dimmed().paint(":"))?;
}
write!(f, " ")?;
}
write!(
f,
"{}{}",
Color::Default.dimmed().paint(&self.target),
Color::Default.dimmed().paint(":"),
)?;
if !self.fields.message.is_empty() {
write!(f, " {}", self.fields.message)?;
}
for (key, value) in self
.fields
.fields
.iter()
.sorted_by_key(|(key, _)| <&String>::clone(key))
{
write!(f, " {}", Color::Default.italic().paint(key))?;
write!(f, "{}", Color::Default.dimmed().paint("="))?;
write!(f, "{}", QuotelessDisplay(value))?;
}
}
Ok(())
}
}
struct QuotelessDisplay<'a>(&'a JsonValue);
impl<'a> Display for QuotelessDisplay<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
QuotelessDisplay(JsonValue::String(str)) => write!(f, "{str}"),
QuotelessDisplay(value) => write!(f, "{value}"),
}
}
}
impl Event {
/// Constructs an Event by parsing a single event from `tracing`'s JSON output.
pub fn from_json_str(s: &str) -> Result<Self> {
serde_json::from_str(s).context(format!("Failed to parse json: {s}"))
}
}