1use 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#[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#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
129pub enum SpanEvent {
130 Open,
131 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 #[must_use]
206 pub fn spans(mut self, spans: Vec<SpanContext>) -> Self {
207 self.spans = spans;
208 self
209 }
210 #[must_use]
213 pub fn span_event(mut self, event: SpanEvent) -> Self {
214 self.span_event = Some(event);
215 self
216 }
217 pub fn span_context(&self) -> &[SpanContext] {
219 &self.spans
220 }
221 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}