rust 120 lines · 3 tabs

Grouping Log Lines by Severity and Emitting a Summary Report in Rust

Shared by codesnips Aug 2026
3 tabs
use once_cell::sync::Lazy;
use regex::Regex;
use std::str::FromStr;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Severity {
    Trace,
    Debug,
    Info,
    Warn,
    Error,
}

#[derive(Debug)]
pub struct ParseSeverityError(pub String);

impl FromStr for Severity {
    type Err = ParseSeverityError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_ascii_uppercase().as_str() {
            "TRACE" => Ok(Severity::Trace),
            "DEBUG" => Ok(Severity::Debug),
            "INFO" => Ok(Severity::Info),
            "WARN" | "WARNING" => Ok(Severity::Warn),
            "ERROR" | "ERR" => Ok(Severity::Error),
            other => Err(ParseSeverityError(other.to_string())),
        }
    }
}

#[derive(Debug)]
pub struct LogLine {
    pub severity: Severity,
    pub message: String,
}

static LINE_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^\s*\[?(?P<level>[A-Za-z]+)\]?\s+(?P<msg>.+)$").unwrap()
});

pub fn parse_line(raw: &str) -> Option<LogLine> {
    let caps = LINE_RE.captures(raw)?;
    let severity = caps.name("level")?.as_str().parse().ok()?;
    let message = caps.name("msg")?.as_str().trim().to_string();
    Some(LogLine { severity, message })
}
3 files · rust Explain with highlit

This snippet builds a small log-analysis pipeline in Rust that reads log lines, classifies each by severity, and prints an aggregated summary. The design separates concerns across three files: a parsing module that turns a raw line into a structured record, an aggregator that accumulates counts and samples, and a main entry point that wires stdin to the report.

In severity.rs, the Severity enum models the fixed set of levels an application emits. Implementing FromStr gives a natural, fallible conversion from a token like WARN into a variant, returning a custom ParseSeverityError for unknown tokens. parse_line uses a lazily-compiled Regex (via once_cell::sync::Lazy) so the pattern is built once and reused across every line, avoiding the classic mistake of recompiling a regex in a hot loop. It returns Option<LogLine> so malformed lines are simply skipped rather than aborting the run.

In report.rs, SeverityReport is the accumulator. Its record method bumps a per-severity counter in a HashMap<Severity, usize> and keeps the first-seen message per level in samples, giving the reader a concrete example without storing every line. The Ord derived on Severity lets the report sort levels from most to least severe when rendering. Implementing Display keeps formatting logic in one place, and total plus dominant expose derived stats. Because record takes ownership of a LogLine, the message String moves into the sample map without a copy.

In main.rs, lines from stdin().lock().lines() are streamed one at a time, so the tool handles arbitrarily large files with constant memory rather than buffering the whole input. Each line is parsed, filtered through filter_map, and folded into the report. The key trade-off is deliberate leniency: unparseable lines are dropped silently, which suits noisy real-world logs but would need a stricter mode for validation use cases. This composition of FromStr, iterators, and a Display accumulator is the idiomatic Rust way to build a focused, testable text-processing tool.


Related snips

Share this code

Here's the card — post it anywhere.

Grouping Log Lines by Severity and Emitting a Summary Report in Rust — share card
Link copied