Skip to main content

tracing_dedup/
lib.rs

1use std::fmt;
2use std::sync::Mutex;
3use tracing::{Event, Subscriber};
4use tracing_subscriber::fmt::format::Writer;
5use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
6use tracing_subscriber::registry::LookupSpan;
7
8#[derive(Clone, Hash, Eq, PartialEq)]
9struct EventKey {
10  message: String,
11  level: tracing::Level,
12  target: String,
13}
14
15pub struct DeduplicatingFormatter<F> {
16  inner: F,
17  state: Mutex<DeduplicationState>,
18}
19
20struct DeduplicationState {
21  last_event: Option<EventKey>,
22  repeat_count: usize,
23}
24
25impl<F> DeduplicatingFormatter<F> {
26  pub fn new(inner: F) -> Self {
27    Self {
28      inner,
29      state: Mutex::new(DeduplicationState {
30        last_event: None,
31        repeat_count: 0,
32      }),
33    }
34  }
35}
36
37impl<S, N, F> FormatEvent<S, N> for DeduplicatingFormatter<F>
38where
39  S: Subscriber + for<'a> LookupSpan<'a>,
40  N: for<'a> FormatFields<'a> + 'static,
41  F: FormatEvent<S, N>,
42{
43  fn format_event(
44    &self,
45    ctx: &FmtContext<'_, S, N>,
46    mut writer: Writer<'_>,
47    event: &Event<'_>,
48  ) -> fmt::Result {
49    let mut visitor = MessageVisitor::default();
50    event.record(&mut visitor);
51
52    let key = EventKey {
53      message: visitor.message,
54      level: *event.metadata().level(),
55      target: event.metadata().target().to_string(),
56    };
57
58    let mut state = self.state.lock().unwrap();
59
60    match &state.last_event {
61      Some(last) if last == &key => {
62        // Same event: suppress it and increment counter
63        state.repeat_count += 1;
64        return Ok(()); // Skip this event
65      }
66      _ => {
67        // Different event: output repeat count if needed
68        if state.repeat_count > 0 {
69          let repeat_count = state.repeat_count + 1;
70          writeln!(writer, "previous message repeated {} times", repeat_count)?;
71        }
72
73        state.last_event = Some(key);
74        state.repeat_count = 0;
75      }
76    }
77
78    drop(state);
79
80    // Format the event using the inner formatter
81    self.inner.format_event(ctx, writer, event)
82  }
83}
84
85#[derive(Default)]
86struct MessageVisitor {
87  message: String,
88}
89
90impl tracing::field::Visit for MessageVisitor {
91  fn record_debug(
92    &mut self,
93    field: &tracing::field::Field,
94    value: &dyn std::fmt::Debug,
95  ) {
96    if field.name() == "message" {
97      self.message = format!("{:?}", value);
98    }
99  }
100
101  fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
102    if field.name() == "message" {
103      self.message = value.to_string();
104    }
105  }
106}