use anyhow::Error;
pub(crate) fn should_log_at(consecutive: u32) -> bool {
consecutive.is_power_of_two()
}
pub struct ConsecutiveErrorLog {
consecutive: u32,
context: &'static str,
}
impl ConsecutiveErrorLog {
pub fn new(context: &'static str) -> Self {
Self { consecutive: 0, context }
}
pub fn note_error(&mut self, err: &Error) {
self.consecutive = self.consecutive.saturating_add(1);
if should_log_at(self.consecutive) {
tracing::error!(
consecutive = self.consecutive,
error = %err,
"{}",
self.context,
);
}
}
pub fn note_success(&mut self) {
if self.consecutive > 0 {
tracing::info!(cleared_after = self.consecutive, "{}: recovered", self.context);
}
self.consecutive = 0;
}
#[cfg(test)]
pub fn consecutive(&self) -> u32 {
self.consecutive
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::anyhow;
#[test]
fn should_log_at_powers_of_two() {
assert!(should_log_at(1));
assert!(should_log_at(2));
assert!(!should_log_at(3));
assert!(should_log_at(4));
assert!(!should_log_at(5));
assert!(!should_log_at(6));
assert!(!should_log_at(7));
assert!(should_log_at(8));
assert!(should_log_at(16));
assert!(should_log_at(1024));
}
#[test]
fn should_log_at_zero_is_false() {
assert!(!should_log_at(0));
}
#[test]
fn note_error_increments_counter() {
let mut log = ConsecutiveErrorLog::new("test");
assert_eq!(log.consecutive(), 0);
log.note_error(&anyhow!("boom"));
assert_eq!(log.consecutive(), 1);
log.note_error(&anyhow!("boom"));
assert_eq!(log.consecutive(), 2);
}
#[test]
fn note_success_resets_counter() {
let mut log = ConsecutiveErrorLog::new("test");
log.note_error(&anyhow!("boom"));
log.note_error(&anyhow!("boom"));
log.note_error(&anyhow!("boom"));
assert_eq!(log.consecutive(), 3);
log.note_success();
assert_eq!(log.consecutive(), 0);
}
#[test]
fn note_success_with_no_prior_errors_is_noop() {
let mut log = ConsecutiveErrorLog::new("test");
log.note_success();
log.note_success();
log.note_success();
assert_eq!(log.consecutive(), 0);
}
#[test]
fn counter_saturates() {
let mut log = ConsecutiveErrorLog::new("test");
log.consecutive = u32::MAX - 1;
log.note_error(&anyhow!("boom"));
assert_eq!(log.consecutive(), u32::MAX);
log.note_error(&anyhow!("boom"));
assert_eq!(log.consecutive(), u32::MAX);
}
}