soma.cell

Web access log analysis over List<Map> entries

Demonstrates filter_by on status ranges, count_by, group_by, agg of latency, distinct paths, and top offenders. Classic log-crunching shape.

A Soma program from the data part of the verified corpus: 59 lines, cells LogAnalysis, LogAnalysisTests. It passes soma check with soma 3.2.4.

How it is built

The cell LogAnalysis has no face and no memory. Handlers: entries (line 6), errors (line 20), error_count (line 22), latency_by_path (line 25), unique_paths (line 31), traffic_by_ip (line 34) and run (line 40).

The file also holds 1 test cell (LogAnalysisTests) with 5 assertions, run by soma test.

Generated from soma describe on this file.

The source

log_analysis.cell

// Web access log analysis over List<Map> entries.
// Demonstrates filter_by on status ranges, count_by, group_by, agg of
// latency, distinct paths, and top offenders. Classic log-crunching shape.

cell LogAnalysis {
    on entries() {
        return list(
            map("ip", "10.0.0.1", "path", "/",       "status", 200, "ms", 12),
            map("ip", "10.0.0.2", "path", "/login",  "status", 200, "ms", 45),
            map("ip", "10.0.0.1", "path", "/api",    "status", 500, "ms", 230),
            map("ip", "10.0.0.3", "path", "/api",    "status", 404, "ms", 8),
            map("ip", "10.0.0.2", "path", "/api",    "status", 500, "ms", 410),
            map("ip", "10.0.0.1", "path", "/login",  "status", 401, "ms", 30),
            map("ip", "10.0.0.4", "path", "/",       "status", 200, "ms", 15),
            map("ip", "10.0.0.3", "path", "/api",    "status", 200, "ms", 99),
            map("ip", "10.0.0.2", "path", "/admin",  "status", 403, "ms", 5)
        )
    }

    on errors() { return entries() |> filter_by("status", ">=", 500) }

    on error_count() { return count_by(entries(), "status", 500) }

    // Average latency per path, slowest first.
    on latency_by_path() {
        return entries()
            |> agg("path", "ms:avg", "ms:max")
            |> sort_by("ms_avg", "desc")
    }

    on unique_paths() { return entries() |> distinct("path") }

    // Requests per IP, busiest first.
    on traffic_by_ip() {
        return entries()
            |> agg("ip", "ms:count")
            |> sort_by("ms_count", "desc")
    }

    on run() {
        print("5xx errors: {error_count()}")
        for r in latency_by_path() { print("{r.path}: avg {r.ms_avg}ms") }
        return unique_paths()
    }
}

cell test LogAnalysisTests {
    rules {
        // two 500s in the log
        assert error_count() == 2
        assert len(errors()) == 2
        // /, /login, /api, /admin = 4 distinct paths
        assert len(unique_paths()) == 4
        // busiest IPs tie at 3 requests (10.0.0.1 and 10.0.0.2)
        assert traffic_by_ip()[0].ms_count == 3
        assert (traffic_by_ip() |> filter_by("ip", "=", "10.0.0.2"))[0].ms_count == 3
    }
}

What the toolchain says

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ soma check log_analysis.cell
✓ All checks passed.
$ soma test log_analysis.cell
test LogAnalysisTests ...
  ✓ assert error_count() == 2
  ✓ assert len(errors()) == 2
  ✓ assert len(unique_paths()) == 4
  ✓ assert traffic_by_ip()[0].ms_count == 3
  ✓ assert (traffic_by_ip() |> filter_by("ip", "=", "10.0.0.2"))[0].ms_count == 3

5 tests: 5 passed, 0 failed
$ soma verify log_analysis.cell
Verifying log_analysis.cell...
No state machine in this program: nothing to prove beyond `soma check` (invariants and lifecycles are what verify proves).
VERIFY OK (vacuous — no state machine)

Builtins used

agg, count_by, distinct, filter_by, len, list, map, print, sort_by

Source file: /corpus/data/log_analysis.cell · examples/corpus/data/log_analysis.cell