Server Log Analyzer
Turn a raw access log into the report an on-call engineer actually reads: request volume, error rate, per-endpoint traffic and p95 latency — the same four numbers every observability dashboard leads with.
How it works in the real world
Every monitoring tool — Datadog, Grafana, CloudWatch — runs this pipeline over your logs:
- Parse — split unstructured log lines into typed fields.
- Aggregate — group by endpoint and count requests and failures.
- Summarise — collapse thousands of latencies into percentiles, because an average hides the slow tail that users feel.
- Report — rank the endpoints so the worst offender is on the first line.
You'll build all four stages. p95 is the number that matters: it means 95% of requests were faster, so it captures the pain an average smooths away.
Parse and count failures
Split each log line into method path status ms. Print the request count, how many had a status of 400 or more, and the error rate to one decimal.
line.split() gives four strings; int(status) >= 400 marks a failure.
logs = [
"GET /api/users 200 143",
"POST /api/orders 201 310",
"GET /api/users 200 98",
"GET /api/health 200 12",
"POST /api/orders 500 842",
"GET /api/users 404 55",
]
errors = 0
for line in logs:
method, path, status, ms = line.split()
if int(status) >= 400:
errors += 1
print(f"Requests: {len(logs)}")
print(f"Errors: {errors}")
print(f"Error rate: {errors / len(logs) * 100:.1f}%")
Run your code to check this step…
Traffic per endpoint
Count requests per path and print path count, busiest first. Break ties alphabetically so the output is stable.
sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])) sorts by count descending then name.
logs = [
"GET /api/users 200 143",
"POST /api/orders 201 310",
"GET /api/users 200 98",
"GET /api/health 200 12",
"POST /api/orders 500 842",
"GET /api/users 404 55",
]
counts = {}
for line in logs:
path = line.split()[1]
counts[path] = counts.get(path, 0) + 1
for path, n in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])):
print(f"{path} {n}")
Run your code to check this step…
Latency percentiles
Write percentile(values, pct) using nearest-rank: sort, then take index ceil(pct/100 * n) - 1. Print the count, p50, p95 and max.
math.ceil(pct / 100 * len(ordered)) gives the rank; subtract 1 for the index.
import math
latencies = [143, 310, 98, 12, 842, 55, 201, 77, 460, 133]
def percentile(values, pct):
ordered = sorted(values)
rank = math.ceil(pct / 100 * len(ordered))
return ordered[rank - 1]
print(f"count: {len(latencies)}")
print(f"p50: {percentile(latencies, 50)}ms")
print(f"p95: {percentile(latencies, 95)}ms")
print(f"max: {max(latencies)}ms")
Run your code to check this step…
Ship the report
Combine everything into an aligned table: header === Traffic Report ===, a column row, one line per endpoint (busiest first) with requests, errors and p95, then a TOTAL row.
f"{path:<14}{n:>5}" left-pads names to 14 and right-aligns numbers in 5. setdefault keeps the per-path accumulator tidy.
import math
logs = [
"GET /api/users 200 143",
"POST /api/orders 201 310",
"GET /api/users 200 98",
"GET /api/health 200 12",
"POST /api/orders 500 842",
"GET /api/users 404 55",
"GET /api/health 200 9",
"POST /api/orders 502 1200",
]
def percentile(values, pct):
ordered = sorted(values)
return ordered[math.ceil(pct / 100 * len(ordered)) - 1]
stats = {}
for line in logs:
method, path, status, ms = line.split()
s = stats.setdefault(path, {"n": 0, "errors": 0, "ms": []})
s["n"] += 1
s["ms"].append(int(ms))
if int(status) >= 400:
s["errors"] += 1
print("=== Traffic Report ===")
print(f"{'endpoint':<14}{'reqs':>5}{'err':>5}{'p95':>7}")
for path, s in sorted(stats.items(), key=lambda kv: -kv[1]["n"]):
print(f"{path:<14}{s['n']:>5}{s['errors']:>5}{percentile(s['ms'], 95):>6}ms")
total = sum(s["n"] for s in stats.values())
errs = sum(s["errors"] for s in stats.values())
print(f"{'TOTAL':<14}{total:>5}{errs:>5}{percentile([int(l.split()[3]) for l in logs], 95):>6}ms")
Run your code to check this step…
Project shipped!
You just built a complete, working server log analyzer — the same architecture running in production software. Pick your next build →