/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use std::fmt::Display; use mail_parser::DateTime; use tokio::io::{AsyncWrite, AsyncWriteExt}; use crate::{Error, Event, EventDetails, Key, Level, Value}; use base64::{Engine, engine::general_purpose::STANDARD}; pub struct FmtWriter { writer: T, ansi: bool, multiline: bool, } #[allow(dead_code)] enum Color { Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, } impl FmtWriter { pub fn new(writer: T) -> Self { Self { writer, ansi: false, multiline: false, } } pub fn with_ansi(self, ansi: bool) -> Self { Self { ansi, ..self } } pub fn with_multiline(self, multiline: bool) -> Self { Self { multiline, ..self } } pub async fn write(&mut self, event: &Event) -> std::io::Result<()> { // Write timestamp if self.ansi { self.writer .write_all(Color::White.as_code().as_bytes()) .await?; } self.writer .write_all( DateTime::from_timestamp(event.inner.timestamp as i64) .to_rfc3339() .as_bytes(), ) .await?; if self.ansi { self.writer.write_all(Color::reset().as_bytes()).await?; } self.writer.write_all(" ".as_bytes()).await?; // Write level if self.ansi { self.writer .write_all( match event.inner.level { Level::Error => Color::Red, Level::Warn => Color::Yellow, Level::Info => Color::Green, Level::Debug => Color::Blue, Level::Trace => Color::Magenta, Level::Disable => return Ok(()), } .as_code_bold() .as_bytes(), ) .await?; } self.writer .write_all(event.inner.level.as_str().as_bytes()) .await?; if self.ansi { self.writer.write_all(Color::reset().as_bytes()).await?; } self.writer.write_all(" ".as_bytes()).await?; // Write message if self.ansi { self.writer .write_all(Color::White.as_code_bold().as_bytes()) .await?; } self.writer .write_all(event.inner.typ.description().as_bytes()) .await?; if self.ansi { self.writer.write_all(Color::reset().as_bytes()).await?; } self.writer.write_all(" (".as_bytes()).await?; self.writer .write_all(event.inner.typ.as_str().as_bytes()) .await?; self.writer .write_all(if self.multiline { ")\n" } else { ") " }.as_bytes()) .await?; // Write keys if let Some(parent_event) = &event.inner.span { self.write_keys(&parent_event.keys, &event.keys, 1).await?; } else { self.write_keys(&[], &event.keys, 1).await?; } if !self.multiline { self.writer.write_all("\n".as_bytes()).await?; } Ok(()) } async fn write_keys( &mut self, span_keys: &[(Key, Value)], keys: &[(Key, Value)], indent: usize, ) -> std::io::Result<()> { Box::pin(async move { let mut is_first = true; for (key, value) in span_keys.iter().chain(keys.iter()) { if matches!(key, Key::SpanId) { continue; } else if is_first { is_first = false; } else if !self.multiline { self.writer.write_all(", ".as_bytes()).await?; } // Write key if self.multiline { for _ in 0..indent { self.writer.write_all("\t".as_bytes()).await?; } } if self.ansi { self.writer .write_all(Color::Cyan.as_code().as_bytes()) .await?; } self.writer.write_all(key.as_str().as_bytes()).await?; if self.ansi { self.writer.write_all(Color::reset().as_bytes()).await?; } // Write value self.writer.write_all(" = ".as_bytes()).await?; self.write_value(value, indent).await?; if self.multiline && !matches!(value, Value::Event(_)) { self.writer.write_all("\n".as_bytes()).await?; } } Ok(()) }) .await } async fn write_value(&mut self, value: &Value, indent: usize) -> std::io::Result<()> { Box::pin(async move { match value { Value::String(v) => { self.writer.write_all("\"".as_bytes()).await?; for ch in v.as_bytes() { match ch { b'\r' => { self.writer.write_all("\\r".as_bytes()).await?; } b'\n' => { self.writer.write_all("\\n".as_bytes()).await?; } b'\t' => { self.writer.write_all("\\t".as_bytes()).await?; } b'\\' => { self.writer.write_all("\\\\".as_bytes()).await?; } _ => { self.writer.write_all(&[*ch]).await?; } } } self.writer.write_all("\"".as_bytes()).await?; } Value::UInt(v) => { self.writer.write_all(v.to_string().as_bytes()).await?; } Value::Int(v) => { self.writer.write_all(v.to_string().as_bytes()).await?; } Value::Float(v) => { self.writer.write_all(v.to_string().as_bytes()).await?; } Value::Timestamp(v) => { self.writer .write_all(DateTime::from_timestamp(*v as i64).to_rfc3339().as_bytes()) .await?; } Value::Duration(v) => { self.writer.write_all(v.to_string().as_bytes()).await?; self.writer.write_all("ms".as_bytes()).await?; } Value::Bytes(bytes) => { self.writer.write_all("base64:".as_bytes()).await?; self.writer .write_all(STANDARD.encode(bytes).as_bytes()) .await?; } Value::Bool(true) => { self.writer.write_all("true".as_bytes()).await?; } Value::Bool(false) => { self.writer.write_all("false".as_bytes()).await?; } Value::Ipv4(v) => { self.writer.write_all(v.to_string().as_bytes()).await?; } Value::Ipv6(v) => { self.writer.write_all(v.to_string().as_bytes()).await?; } Value::Event(e) => { self.writer .write_all(e.0.inner.description().as_bytes()) .await?; self.writer.write_all(" (".as_bytes()).await?; self.writer.write_all(e.0.inner.as_str().as_bytes()).await?; self.writer.write_all(")".as_bytes()).await?; if !e.0.keys.is_empty() { self.writer .write_all(if self.multiline { "\n" } else { " { " }.as_bytes()) .await?; self.write_keys(&e.0.keys, &[], indent + 1).await?; if !self.multiline { self.writer.write_all(" }".as_bytes()).await?; } } else if self.multiline { self.writer.write_all("\n".as_bytes()).await?; } } Value::Array(arr) => { self.writer.write_all("[".as_bytes()).await?; for (pos, value) in arr.iter().enumerate() { if pos > 0 { self.writer.write_all(", ".as_bytes()).await?; } self.write_value(value, indent).await?; } self.writer.write_all("]".as_bytes()).await?; } Value::None => { self.writer.write_all("(null)".as_bytes()).await?; } } Ok(()) }) .await } pub async fn flush(&mut self) -> std::io::Result<()> { self.writer.flush().await } pub fn update_writer(&mut self, writer: T) { self.writer = writer; } } impl Color { pub fn as_code(&self) -> &'static str { match self { Color::Black => "\x1b[30m", Color::Red => "\x1b[31m", Color::Green => "\x1b[32m", Color::Yellow => "\x1b[33m", Color::Blue => "\x1b[34m", Color::Magenta => "\x1b[35m", Color::Cyan => "\x1b[36m", Color::White => "\x1b[37m", } } pub fn as_code_bold(&self) -> &'static str { match self { Color::Black => "\x1b[30;1m", Color::Red => "\x1b[31;1m", Color::Green => "\x1b[32;1m", Color::Yellow => "\x1b[33;1m", Color::Blue => "\x1b[34;1m", Color::Magenta => "\x1b[35;1m", Color::Cyan => "\x1b[36;1m", Color::White => "\x1b[37;1m", } } pub fn reset() -> &'static str { "\x1b[0m" } } impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Value::String(value) => value.fmt(f), Value::UInt(value) => value.fmt(f), Value::Int(value) => value.fmt(f), Value::Float(value) => value.fmt(f), Value::Timestamp(value) => value.fmt(f), Value::Duration(value) => value.fmt(f), Value::Bytes(value) => STANDARD.encode(value).fmt(f), Value::Bool(value) => value.fmt(f), Value::Ipv4(value) => value.fmt(f), Value::Ipv6(value) => value.fmt(f), Value::Event(value) => { "{".fmt(f)?; value.fmt(f)?; "}".fmt(f) } Value::Array(value) => { f.write_str("[")?; for (i, value) in value.iter().enumerate() { if i > 0 { f.write_str(", ")?; } value.fmt(f)?; } f.write_str("]") } Value::None => "(null)".fmt(f), } } } impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.inner.description().fmt(f)?; " (".fmt(f)?; self.0.inner.as_str().fmt(f)?; ")".fmt(f)?; if !self.0.keys.is_empty() { f.write_str(": ")?; for (i, (key, value)) in self.0.keys.iter().enumerate() { if i > 0 { f.write_str(", ")?; } key.as_str().fmt(f)?; f.write_str(" = ")?; value.fmt(f)?; } } Ok(()) } }