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.
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
$ 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