RustArena

Step 2 of 6

LOG-102: the daily traffic report

LOG-102: Daily traffic report

Product managers want a morning summary of yesterday's traffic: how many requests, how many failed, which endpoints are busy and slow. The log comes in on standard input. Bad lines must not crash the report; count them instead.

This is the bread and butter of backend work: read records, aggregate, print a table. The tools:

use std::collections::BTreeMap;

#[derive(Default)]
struct RegionStats {
    orders: u32,
    total_cents: u64,
    biggest_cents: u64,
}

fn main() {
    let sales = [("north", 1250), ("south", 300), ("north", 4990), ("east", 2100), ("south", 700)];
    let mut by_region: BTreeMap<&str, RegionStats> = BTreeMap::new();
    for &(region, cents) in &sales {
        let stats = by_region.entry(region).or_default();
        stats.orders += 1;
        stats.total_cents += cents;
        stats.biggest_cents = stats.biggest_cents.max(cents);
    }
    println!("{:<8}{:>7}{:>10}{:>10}", "region", "orders", "average", "biggest");
    for (region, s) in &by_region {
        let average = s.total_cents as f64 / s.orders as f64 / 100.0;
        println!("{:<8}{:>7}{:>10.2}{:>10.2}", region, s.orders, average, s.biggest_cents as f64 / 100.0);
    }
    let big = sales.iter().filter(|&&(_, cents)| cents >= 1000).count();
    println!("{big} orders of 10.00 or more ({:.1}%)", big as f64 * 100.0 / sales.len() as f64);
}
region   orders   average   biggest
east          1     21.00     21.00
north         2     31.20     49.90
south         2      5.00      7.00
3 orders of 10.00 or more (60.0%)

#[derive(Default)] gives the struct a zeroed starting value, so entry(...).or_default() can create a fresh one the first time a region appears.

Common mistakes

Your task

Write the report. The seed has the LogEntry parser from LOG-101 (simplified to String errors). Read every line of standard input; lines that don't parse (blank lines included) are skipped and counted. Print exactly:

requests: 7
skipped: 1
errors: 2 (28.6%)
methods: GET 6, POST 1
endpoints:
  /api/orders     3 req  avg   240 ms  max   300 ms
  /api/users      2 req  avg    45 ms  max    50 ms
  /health         2 req  avg     1 ms  max     1 ms
slowest: /api/orders 300 ms at 2026-10-01T09:00:07Z

Challenge 2

LOG-105: Requests per hour

The capacity planning team wants a quick text histogram of traffic per hour of the day.

Write a program that reads the same log format from standard input and prints one line per hour that had traffic, in order: the hour as HH:00, a bar with one # per request, and the count:

09:00 ### 3
14:00 # 1

A line counts if it has exactly 5 fields and its timestamp has a two-digit hour at character positions 11 and 12, counting from 0 (the 09 in 2026-10-01T09:00:03Z), from 00 to 23; skip everything else. Use a BTreeMap keyed by hour. If nothing counts, print no traffic.

Challenge 3

LOG-108: Page the on-call engineer on error bursts

One 5xx error is noise. Several within a minute means something is broken. We want an alert tool for the log stream.

This log uses Unix timestamps (seconds) in the first field: 1727773200 GET /api/orders 503 120. Timestamps never go backwards. The alert threshold is the program's first command-line argument (for example 3). If it's missing or not a positive number, print usage: alerts THRESHOLD to standard error and exit with code 2.

Keep the times of recent server errors (status 500 or above) in a VecDeque. For each error at time t, first drop errors from the front that are 60 or more seconds older than t, then add t. When the window holds THRESHOLD errors, print ALERT at T: N server errors in 60s and clear the window (so one burst gives one alert). Skip malformed lines. At the end print alerts: K.

Previous: LOG-101: parse the access log Next: SHOP-210: orders with validation