← thecodex.expert · The Codex Family of Knowledge
Tier 3 · Upper-Intermediate · Python Project

Log Analyser

Parse server log files to count errors, find the busiest hours, and spot the top offenders. Turn raw logs into insight.

🧠 Teaches how to think spoonfed, every age Last verified:

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.

Where this shows up: monitoring and observability, debugging production issues, security analysis, performance tuning. When something breaks at 3am, the person who can analyse the logs is the one who fixes it.

2 How to Think About It

Think about turning lines into counts, before any code:

The plan — in plain English
1. Read the log file line by line. → 2. Parse each line into its parts: timestamp, level (INFO/ERROR), message. → 3. Count as you go: errors, entries per hour, message frequencies. → 4. Report the totals and the top items.

Read log lines

Parse each: time, level, message

Count errors

Count entries per hour

Count message frequency

Report insights

3 The Build — explained part by part

Here is the complete analyser. Each part is explained below.

Pythonloganalyse.py
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']}")
What each part does — in plain words
from collections import Counter — a special dictionary built for counting. 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.
Common mistakes — and how to avoid them
✗ Using plain split() — the message gets chopped at every space.
✓ Use split(" ", 3) to keep the message whole.
✗ Counting with a manual dictionary and forgetting to initialise keys — KeyError.
✓ Use Counter, which starts every key at 0.
✗ Loading the whole file into memory for a huge log — wasteful.
✓ Iterate line by line (the file object is already iterable).

4 Test & Prove Each Part

We test parsing a log line and the aggregation, using a few known lines.

A line parses into hour, level, and message
Errors are counted correctly
The busiest hour is identified
Pythontest_loganalyse.py
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

INPUTlog filetimestamped log lines
Format
2026-06-24 14:30:00 ERROR Database timeout
OUTPUTreporttotals + top items
Returns
Total: 1024  Errors: 37
Busiest hour: ("14", 210)
Top errors: [("Database timeout", 12), ...]

6 Run It & Automate It

Run it locally
python3 loganalyse.py
Point it at a server.log file to get a full report.

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
Total entries: 1024
Errors: 37
Busiest hour: ('14', 210)
Top errors: [('Database timeout', 12), ('Disk full', 8)]
If it breaks — how to fix it
🚨 ValueError: not enough values to unpack.
A line did not have all parts. The len(parts) < 4 guard handles malformed lines — make sure it is there.
🚨 Counts look wrong.
Check your log format matches the parser, especially the position of the level field.
GroovyJenkinsfile
// 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.' }
    }
}
🎯 Try this next — make it yours
  1. Date filtering. Analyse only a chosen day. (Teaches: filtering by date.)
  2. Regex parsing. Handle varied log formats with regular expressions. (Teaches: regex.)
  3. Live tail. Watch a log as new lines arrive. (Teaches: following a file.)
What you learned
You learned to parse semi-structured text at scale and aggregate it with 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.