Skip to main content

rich_ext/
event.rs

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