Skip to main content

rich_ext/
event.rs

1//! Typed, ordered events. Time and context are supplied by the caller.
2use crate::layout::{OverflowPolicy, fit_segments};
3use rich::{Console, ConsoleOptions, Renderable, Segment, Style, Text};
4#[derive(Clone, Debug, PartialEq)]
5pub enum Value {
6    Null,
7    Bool(bool),
8    Integer(i64),
9    Unsigned(u64),
10    Integer128(i128),
11    Unsigned128(u128),
12    Float(f64),
13    String(String),
14    List(Vec<Value>),
15    Map(Vec<(String, Value)>),
16}
17impl Value {
18    pub(crate) fn format(&self, expanded: bool, depth: usize) -> String {
19        match self {
20            Self::Null => "null".into(),
21            Self::Bool(v) => v.to_string(),
22            Self::Integer(v) => v.to_string(),
23            Self::Unsigned(v) => v.to_string(),
24            Self::Integer128(v) => v.to_string(),
25            Self::Unsigned128(v) => v.to_string(),
26            Self::Float(v) => v.to_string(),
27            Self::String(v) => v.clone(),
28            Self::List(values) => {
29                let items: Vec<_> = values
30                    .iter()
31                    .map(|v| v.format(expanded, depth + 1))
32                    .collect();
33                collection("[", "]", items, expanded, depth)
34            }
35            Self::Map(values) => {
36                let items: Vec<_> = values
37                    .iter()
38                    .map(|(k, v)| format!("{k}: {}", v.format(expanded, depth + 1)))
39                    .collect();
40                collection("{", "}", items, expanded, depth)
41            }
42        }
43    }
44}
45fn collection(open: &str, close: &str, items: Vec<String>, expanded: bool, depth: usize) -> String {
46    if !expanded || items.is_empty() {
47        return format!("{open}{}{close}", items.join(", "));
48    }
49    let indent = "  ".repeat(depth.saturating_add(1));
50    format!(
51        "{open}\n{indent}{}\n{}{close}",
52        items.join(&format!(",\n{indent}")),
53        "  ".repeat(depth)
54    )
55}
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub enum Message {
58    Literal(String),
59    Markup(String),
60}
61/// How serious an event is. Variants are in increasing order, so `Warn <
62/// Error`.
63#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub enum Severity {
65    Trace,
66    Debug,
67    Info,
68    Warn,
69    Error,
70    Fatal,
71}
72impl Severity {
73    pub fn as_str(self) -> &'static str {
74        match self {
75            Self::Trace => "trace",
76            Self::Debug => "debug",
77            Self::Info => "info",
78            Self::Warn => "warn",
79            Self::Error => "error",
80            Self::Fatal => "fatal",
81        }
82    }
83}
84#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
85pub enum EventView {
86    #[default]
87    Compact,
88    Expanded,
89}
90#[derive(Clone, Debug, PartialEq, Eq)]
91pub struct SourceLocation {
92    pub path: String,
93    pub line: usize,
94    pub column: Option<usize>,
95}
96#[derive(Clone, Debug, Default, PartialEq, Eq)]
97pub struct EventContext {
98    pub timestamp: Option<String>,
99    pub severity: Option<Severity>,
100    pub target: Option<String>,
101    pub module: Option<String>,
102    pub source: Option<SourceLocation>,
103    pub thread: Option<String>,
104    pub task: Option<String>,
105    pub correlation_id: Option<String>,
106}
107/// A span an event happened in: its name and its fields as last recorded.
108#[derive(Clone, Debug, PartialEq)]
109pub struct SpanContext {
110    pub name: String,
111    pub fields: Vec<(String, Value)>,
112}
113impl SpanContext {
114    pub fn new(name: impl Into<String>) -> Self {
115        Self {
116            name: name.into(),
117            fields: Vec::new(),
118        }
119    }
120    #[must_use]
121    pub fn field(mut self, key: impl Into<String>, value: Value) -> Self {
122        self.fields.push((key.into(), value));
123        self
124    }
125}
126/// Marks an event that reports a span opening or closing rather than
127/// something that happened inside one.
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
129pub enum SpanEvent {
130    Open,
131    /// The span closed after this long, from opening to closing.
132    Close {
133        elapsed: std::time::Duration,
134    },
135}
136#[derive(Clone, Debug)]
137pub struct StructuredEvent {
138    diagnostics: Vec<crate::diagnostic::Diagnostic>,
139    spans: Vec<SpanContext>,
140    span_event: Option<SpanEvent>,
141    pub message: Message,
142    pub fields: Vec<(String, Value)>,
143    pub context: EventContext,
144    order: Vec<String>,
145    hidden: Vec<String>,
146    view: EventView,
147    overflow: OverflowPolicy,
148}
149impl StructuredEvent {
150    pub fn new(message: Message) -> Self {
151        Self {
152            diagnostics: Vec::new(),
153            spans: Vec::new(),
154            span_event: None,
155            message,
156            fields: Vec::new(),
157            context: EventContext::default(),
158            order: Vec::new(),
159            hidden: Vec::new(),
160            view: EventView::Compact,
161            overflow: OverflowPolicy::Fold,
162        }
163    }
164    #[must_use]
165    pub fn diagnostic(mut self, diagnostic: crate::diagnostic::Diagnostic) -> Self {
166        self.diagnostics.push(diagnostic);
167        self
168    }
169    #[must_use]
170    pub fn field(mut self, key: impl Into<String>, value: Value) -> Self {
171        let key = key.into();
172        if let Some((_, old)) = self.fields.iter_mut().find(|(name, _)| name == &key) {
173            *old = value;
174        } else {
175            self.fields.push((key, value));
176        }
177        self
178    }
179    #[must_use]
180    pub fn field_order(mut self, keys: Vec<String>) -> Self {
181        self.order = keys;
182        self
183    }
184    #[must_use]
185    pub fn hide_fields(mut self, keys: Vec<String>) -> Self {
186        self.hidden = keys;
187        self
188    }
189    #[must_use]
190    pub fn view(mut self, view: EventView) -> Self {
191        self.view = view;
192        self
193    }
194    #[must_use]
195    pub fn context(mut self, context: EventContext) -> Self {
196        self.context = context;
197        self
198    }
199    #[must_use]
200    pub fn overflow(mut self, overflow: OverflowPolicy) -> Self {
201        self.overflow = overflow;
202        self
203    }
204    /// The spans the event happened in, outermost first.
205    #[must_use]
206    pub fn spans(mut self, spans: Vec<SpanContext>) -> Self {
207        self.spans = spans;
208        self
209    }
210    /// Mark the event as a span opening or closing. Its message is the span's
211    /// name, its fields the span's, and `spans` its parents.
212    #[must_use]
213    pub fn span_event(mut self, event: SpanEvent) -> Self {
214        self.span_event = Some(event);
215        self
216    }
217    /// The spans the event happened in, outermost first.
218    pub fn span_context(&self) -> &[SpanContext] {
219        &self.spans
220    }
221    /// Whether the event reports a span opening or closing.
222    pub fn span_marker(&self) -> Option<SpanEvent> {
223        self.span_event
224    }
225}
226pub(crate) fn theme_style(console: &Console, key: &str, fallback: &str) -> Style {
227    console
228        .theme()
229        .get(key)
230        .cloned()
231        .unwrap_or_else(|| Style::parse(fallback).unwrap_or_default())
232}
233pub(crate) fn flatten(lines: Vec<Vec<Segment>>) -> Vec<Segment> {
234    let count = lines.len();
235    lines
236        .into_iter()
237        .enumerate()
238        .flat_map(|(i, mut row)| {
239            if i + 1 < count {
240                row.push(Segment::line());
241            }
242            row
243        })
244        .collect()
245}
246impl Renderable for StructuredEvent {
247    fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
248        if options.max_width == 0 || options.height == Some(0) {
249            return Vec::new();
250        }
251        let mut text = Text::new("");
252        if let Some(timestamp) = &self.context.timestamp {
253            text.append(&format!("{timestamp} "), None);
254        }
255        if let Some(severity) = self.context.severity {
256            let fallback = match severity {
257                Severity::Error | Severity::Fatal => "bold red",
258                Severity::Warn => "yellow",
259                Severity::Info => "green",
260                _ => "dim",
261            };
262            text.append(
263                &format!("{} ", severity.as_str()),
264                Some(
265                    theme_style(
266                        console,
267                        &format!("event.severity.{}", severity.as_str()),
268                        fallback,
269                    )
270                    .into(),
271                ),
272            );
273        }
274        for (label, value) in [
275            ("target", &self.context.target),
276            ("module", &self.context.module),
277            ("thread", &self.context.thread),
278            ("task", &self.context.task),
279            ("correlation", &self.context.correlation_id),
280        ] {
281            if let Some(value) = value {
282                text.append(&format!("{label}={value} "), None);
283            }
284        }
285        if let Some(source) = &self.context.source {
286            text.append(
287                &format!(
288                    "{}:{}{} ",
289                    source.path,
290                    source.line,
291                    source.column.map(|c| format!(":{c}")).unwrap_or_default()
292                ),
293                None,
294            );
295        }
296        let mut message = match &self.message {
297            Message::Literal(s) => Text::new(s),
298            Message::Markup(s) => Text::from_markup(s).unwrap_or_else(|_| Text::new(s)),
299        };
300        message.set_base_style(theme_style(console, "event.message", ""));
301        text = text.append_text(&message);
302        let mut selected = Vec::new();
303        for name in &self.order {
304            if let Some(index) = self.fields.iter().position(|(k, _)| k == name)
305                && !selected.contains(&index)
306            {
307                selected.push(index);
308            }
309        }
310        for index in 0..self.fields.len() {
311            if !selected.contains(&index) {
312                selected.push(index);
313            }
314        }
315        for index in selected {
316            let (name, value) = &self.fields[index];
317            if self.hidden.contains(name) {
318                continue;
319            }
320            text.append(
321                if self.view == EventView::Compact {
322                    " "
323                } else {
324                    "\n"
325                },
326                None,
327            );
328            text.append(
329                &format!("{name}="),
330                Some(theme_style(console, "event.field", "cyan").into()),
331            );
332            text.append(
333                &value.format(self.view == EventView::Expanded, 0),
334                Some(theme_style(console, "event.value", "").into()),
335            );
336        }
337        let mut lines = fit_segments(
338            &text.render(console.theme(), &Style::new()),
339            options.max_width,
340            self.overflow,
341        );
342        for diagnostic in &self.diagnostics {
343            let mut diagnostic_options = options.clone();
344            diagnostic_options.height = None;
345            lines.extend(Segment::split_lines(
346                &diagnostic.rich_render(console, &diagnostic_options),
347            ));
348        }
349        if let Some(height) = options.height {
350            lines.truncate(height);
351        }
352        flatten(lines)
353    }
354}