Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve event formatting #411

Merged
merged 6 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions retis-events/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::fmt;

use chrono::{DateTime, Utc};

use crate::{event_section, event_type, *};
use crate::*;

/// Startup event section. Contains global information about a collection as a
/// whole, with data gathered at collection startup time.
Expand All @@ -15,7 +15,7 @@ pub struct StartupEvent {
}

impl EventFmt for StartupEvent {
fn event_fmt(&self, f: &mut fmt::Formatter, _: &DisplayFormat) -> fmt::Result {
fn event_fmt(&self, f: &mut Formatter, _: &DisplayFormat) -> fmt::Result {
write!(f, "Retis version {}", self.retis_version)
}
}
Expand Down Expand Up @@ -44,7 +44,7 @@ pub struct CommonEvent {
}

impl EventFmt for CommonEvent {
fn event_fmt(&self, f: &mut fmt::Formatter, format: &DisplayFormat) -> fmt::Result {
fn event_fmt(&self, f: &mut Formatter, format: &DisplayFormat) -> fmt::Result {
match format.time_format {
TimeFormat::MonotonicTimestamp => write!(f, "{}", self.timestamp)?,
TimeFormat::UtcDate => match format.monotonic_offset {
Expand Down
6 changes: 3 additions & 3 deletions retis-events/src/ct.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt;

use super::*;
use crate::{event_section, event_type};
use crate::{event_section, event_type, Formatter};

#[event_type]
#[derive(Default)]
Expand Down Expand Up @@ -128,7 +128,7 @@ pub struct CtConnEvent {
}

impl EventFmt for CtEvent {
fn event_fmt(&self, f: &mut fmt::Formatter, _: &DisplayFormat) -> fmt::Result {
fn event_fmt(&self, f: &mut Formatter, _: &DisplayFormat) -> fmt::Result {
use CtState::*;
match self.state {
Established => write!(f, "ct_state ESTABLISHED ")?,
Expand All @@ -152,7 +152,7 @@ impl EventFmt for CtEvent {
}

impl CtEvent {
fn format_conn(conn: &CtConnEvent, f: &mut fmt::Formatter) -> fmt::Result {
fn format_conn(conn: &CtConnEvent, f: &mut Formatter) -> fmt::Result {
match (&conn.orig.proto, &conn.reply.proto) {
(CtProto::Tcp(tcp_orig), CtProto::Tcp(tcp_reply)) => {
write!(
Expand Down
201 changes: 189 additions & 12 deletions retis-events/src/display.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use std::fmt;
use std::{
fmt::{self, Write},
result, str,
};

use log::warn;

use super::TimeSpec;

Expand Down Expand Up @@ -45,14 +50,177 @@ impl DisplayFormat {
}
}

/// `Formatter` implements `std::fmt::Write` and controls how events are being
/// displayed. This is similar to `std::fmt::Formatter` but with our own
/// constraints.
///
/// It supports the following capabilities: indentation and itemization. Each of
/// those are always context-based: the capabilities and their configuration can
/// change over time and might end based on input (eg. itemization).
pub struct Formatter<'a, 'inner> {
inner: &'a mut fmt::Formatter<'inner>,
pub conf: FormatterConf,
/// Indentation level (in spaces).
level: usize,
/// True if the next input is the start of a block (aka. first call to
/// `flush_buf`).
first: bool,
/// True if the next input is the start of a line.
start: bool,
atenart marked this conversation as resolved.
Show resolved Hide resolved
/// Buffer holding the output before being flushed.
buf: String,
}

impl<'a, 'inner> Formatter<'a, 'inner> {
pub fn new(
inner: &'a mut fmt::Formatter<'inner>,
conf: FormatterConf,
) -> Formatter<'a, 'inner> {
let level = conf.level;

Self {
inner,
conf,
level,
first: true,
start: true,
buf: String::with_capacity(4096usize),
}
}

/// Directly implement write_fmt to avoid the need of an explicit
/// `use fmt::Write` by every user. See the `std::write` documentation.
#[inline]
pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> result::Result<(), fmt::Error> {
<Self as fmt::Write>::write_fmt(self, args)
}

pub fn flush_buf(&mut self) -> result::Result<(), fmt::Error> {
let first = self.first;
match self.buf.is_empty() {
true => return Ok(()),
false => self.first = false,
}

let mut lines = self.buf.split('\n');

// Compute the prefix including the itemization char, if any.
let mut prefix = " ".repeat(self.level);
if first && self.level >= 2 {
if let Some(item) = self.conf.item {
prefix.replace_range(self.level - 2..self.level - 1, &item.to_string());
}
}

if let Some(line) = lines.next() {
if self.start {
self.start = false;
self.inner.write_str(&prefix)?;
}
self.inner.write_str(line)?;
}

// Reset the itemization char, if any.
if first && self.level >= 2 && self.conf.item.is_some() {
prefix = " ".repeat(self.level);
}

lines.try_for_each(|line| {
self.inner.write_char('\n')?;
self.inner.write_str(&prefix)?;
self.inner.write_str(line)
})?;

if self.buf.ends_with('\n') {
self.inner.write_char('\n')?;
self.start = true;
}

self.buf.clear();
Ok(())
}
}

impl fmt::Write for Formatter<'_, '_> {
fn write_str(&mut self, s: &str) -> result::Result<(), fmt::Error> {
if self.conf.level != self.level {
if !self.buf.is_empty() {
self.flush_buf()?;
}
self.level = self.conf.level;
}

self.buf.push_str(s);
Ok(())
}
}

impl Drop for Formatter<'_, '_> {
fn drop(&mut self) {
if !self.buf.is_empty() {
self.flush_buf().expect("Could not flush Formatter buffer");
}
}
}

/// Configuration for the `Formatter`. It can be shared between multiple
/// `EventDisplay::display` calls but its scope is restricted to a single call.
/// This means a base configuration can be shared for multiple
/// `EventDisplay::display` call but any modification made within an
/// `EventDisplay::display` call won't be visibile outside.
#[derive(Clone, Default)]
pub struct FormatterConf {
level: usize,
saved_levels: Vec<usize>,
item: Option<char>,
}

impl FormatterConf {
pub fn new() -> Self {
Self::with_level(0)
}

pub fn with_level(level: usize) -> Self {
Self {
level,
..Default::default()
}
}

/// Increase the indentation level by `diff`.
pub fn inc_level(&mut self, diff: usize) {
self.saved_levels.push(self.level);
self.level += diff;
}

/// Reset the indentation level to its previous value.
pub fn reset_level(&mut self) {
match self.saved_levels.pop() {
Some(level) => {
self.level = level;
}
None => warn!("Cannot reset the indentation level"),
}
}

/// Set an itemization char to be printed at the start of output, or None.
pub fn set_item(&mut self, item: Option<char>) {
self.item = item;
}
}

/// Trait controlling how an event or an event section (or any custom type
/// inside it) is displayed. It works by providing an helper returning an
/// implementation of the std::fmt::Display trait, which can be used later to
/// provide different formats. It is also interesting as those helpers can take
/// arguments, unlike a plain std::fmt::Display implementation.
pub trait EventDisplay<'a>: EventFmt {
/// Display the event using the default event format.
fn display(&'a self, format: &'a DisplayFormat) -> Box<dyn fmt::Display + 'a>;
fn display(
&'a self,
format: &'a DisplayFormat,
conf: &'a FormatterConf,
) -> Box<dyn fmt::Display + 'a>;
}

/// Trait controlling how an event or an event section (or any custom type
Expand All @@ -64,26 +232,33 @@ pub trait EventDisplay<'a>: EventFmt {
/// members if any.
pub trait EventFmt {
/// Default formatting of an event.
fn event_fmt(&self, f: &mut fmt::Formatter, format: &DisplayFormat) -> fmt::Result;
fn event_fmt(&self, f: &mut Formatter, format: &DisplayFormat) -> fmt::Result;
}

impl<'a, T> EventDisplay<'a> for T
where
T: EventFmt,
{
fn display(&'a self, format: &'a DisplayFormat) -> Box<dyn fmt::Display + 'a> {
fn display(
&'a self,
format: &'a DisplayFormat,
conf: &'a FormatterConf,
) -> Box<dyn fmt::Display + 'a> {
struct DefaultDisplay<'a, U> {
myself: &'a U,
format: &'a DisplayFormat,
conf: &'a FormatterConf,
}
impl<U: EventFmt> fmt::Display for DefaultDisplay<'_, U> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.myself.event_fmt(f, self.format)
self.myself
.event_fmt(&mut Formatter::new(f, self.conf.clone()), self.format)
}
}
Box::new(DefaultDisplay {
myself: self,
format,
conf,
})
}
}
Expand All @@ -95,23 +270,25 @@ where
///
/// ```
/// use std::fmt;
/// use retis_events::DelimWriter;
/// use retis_events::{Formatter, FormatterConf, DelimWriter};
///
/// struct Flags {
/// opt1: bool,
/// opt2: bool,
/// }
/// impl fmt::Display for Flags {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "flags")?;
/// let mut f = Formatter::new(f, FormatterConf::new());
///
/// write!(&mut f, "flags")?;
/// let mut space = DelimWriter::new(' ');
/// if self.opt1 {
/// space.write(f)?;
/// write!(f, "opt1");
/// space.write(&mut f)?;
/// write!(&mut f, "opt1");
/// }
/// if self.opt2 {
/// space.write(f)?;
/// write!(f, "opt2")?;
/// space.write(&mut f)?;
/// write!(&mut f, "opt2")?;
/// }
/// Ok(())
/// }
Expand All @@ -129,7 +306,7 @@ impl DelimWriter {
}

/// If it's not the first time it's called, write the delimiter.
pub fn write(&mut self, f: &mut fmt::Formatter) -> fmt::Result {
pub fn write(&mut self, f: &mut Formatter) -> fmt::Result {
match self.first {
true => self.first = false,
false => write!(f, "{}", self.delim)?,
Expand Down
Loading