1 The Problem
We want a log analyser: read a server log file, count how many entries are errors versus normal, find which hour had the most traffic, and list the most frequent error messages. It teaches parsing semi-structured text at scale and aggregating it into useful insight — a daily task in operations.
2 How to Think About It
Think about turning lines into counts, before any code:
3 The Build — explained part by part
Here is the complete analyser. Each part is explained below.
from collections import Counter
def parse_line(line):
# Expected format: "2026-06-24 14:30:00 ERROR Database timeout"
parts = line.split(" ", 3)
if len(parts) < 4:
return None
date, time, level, message = parts
return {"hour": time[:2], "level": level, "message": message.strip()}
def analyse(lines):
levels = Counter()
hours = Counter()
errors = Counter()
for line in lines:
entry = parse_line(line)
if not entry:
continue
levels[entry["level"]] += 1
hours[entry["hour"]] += 1
if entry["level"] == "ERROR":
errors[entry["message"]] += 1
return {
"total": sum(levels.values()),
"errors": levels["ERROR"],
"busiest_hour": hours.most_common(1)[0] if hours else None,
"top_errors": errors.most_common(3),
}
if __name__ == "__main__":
with open("server.log") as f:
report = analyse(f)
print(f"Total entries: {report['total']}")
print(f"Errors: {report['errors']}")
print(f"Busiest hour: {report['busiest_hour']}")
print(f"Top errors: {report['top_errors']}")
counter[key] += 1 just works, and it has handy methods like most_common.parse_line — split each line into its parts.
line.split(" ", 3) splits on the first 3 spaces only, so the message (which may contain spaces) stays whole. We return None for malformed lines.time[:2] — the first two characters of the time (
"14:30:00" → "14") give the hour, for grouping traffic by hour.three Counters — we tally levels, hours, and error messages in one pass through the file. Counting everything in a single loop is efficient even for huge logs.
hours.most_common(1) — the busiest hour. errors.most_common(3) — the top 3 error messages.
Counter does the sorting for us.
split() — the message gets chopped at every space.split(" ", 3) to keep the message whole.KeyError.Counter, which starts every key at 0.4 Test & Prove Each Part
We test parsing a log line and the aggregation, using a few known lines.
from collections import Counter
def parse_line(line):
parts = line.split(" ", 3)
if len(parts) < 4:
return None
date, time, level, message = parts
return {"hour": time[:2], "level": level, "message": message.strip()}
def analyse(lines):
levels, hours = Counter(), Counter()
for line in lines:
entry = parse_line(line)
if entry:
levels[entry["level"]] += 1
hours[entry["hour"]] += 1
return {"errors": levels["ERROR"],
"busiest_hour": hours.most_common(1)[0] if hours else None}
LINES = [
"2026-06-24 14:30:00 ERROR Database timeout",
"2026-06-24 14:45:00 INFO Request served",
"2026-06-24 15:00:00 ERROR Disk full",
]
def test_parse():
"""A line parses into its parts."""
entry = parse_line(LINES[0])
assert entry["hour"] == "14"
assert entry["level"] == "ERROR"
def test_error_count():
"""Two errors are counted."""
assert analyse(LINES)["errors"] == 2
def test_busiest_hour():
"""Hour 14 has the most entries."""
assert analyse(LINES)["busiest_hour"][0] == "14"
Run with pytest -v. We feed analyse a few known log lines so every count can be checked by hand. This is how you trust an analyser before running it on millions of real lines.
5 The Interface
Format
2026-06-24 14:30:00 ERROR Database timeoutReturns
Total: 1024 Errors: 37
Busiest hour: ("14", 210)
Top errors: [("Database timeout", 12), ...]6 Run It & Automate It
python3 loganalyse.pyPoint it at a
server.log file to get a full report.Jenkins runs the tests automatically — each line explained below.
Total entries: 1024
Errors: 37
Busiest hour: ('14', 210)
Top errors: [('Database timeout', 12), ('Disk full', 8)]ValueError: not enough values to unpack.len(parts) < 4 guard handles malformed lines — make sure it is there.// Jenkinsfile — automated tests on every change.
pipeline {
agent any
stages {
stage('Get the code') { steps { checkout scm } } // download the code
stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } } // test tool
stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } } // run every test
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
- Date filtering. Analyse only a chosen day. (Teaches: filtering by date.)
- Regex parsing. Handle varied log formats with regular expressions. (Teaches: regex.)
- Live tail. Watch a log as new lines arrive. (Teaches: following a file.)
Counter — counting categories, grouping by time, and finding top items in a single pass. Turning raw logs into insight is a vital operations skill. Related: collections, logging.