tokio_bin_process/
event.rs

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