DEV Community

Sam Rivera
Sam Rivera

Posted on

I Taught a Free Model to Read 10,000 Log Lines So I Could Sleep

My side project's log file hit 40MB on a Tuesday night.

I hadn't opened it in three weeks. The errors were buried under framework noise, and the noise was winning. I needed a triage system. I had a free server and a free model endpoint from MonkeyCode. I also had a stubborn belief that this could work.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The plan

One cron job. One Python script. One model endpoint. Zero dollars.

The server collects log lines every hour. The model classifies each line as critical, warning, or noise. I only read the critical ones.

Step 1: Collect

import subprocess

LOG_PATH = "/var/log/myapp/app.log"

def tail_logs(n=200):
    result = subprocess.run(
        ["tail", "-n", str(n), LOG_PATH],
        capture_output=True, text=True
    )
    return result.stdout.strip().split("\n")
Enter fullscreen mode Exit fullscreen mode

No fancy log shippers. Just tail.

Step 2: Classify

import json
import urllib.request

def classify(lines):
    prompt = (
        "Classify each log line as critical, warning, or noise. "
        "Return a JSON array of objects with 'line' and 'level' keys.\n\n"
        + "\n".join(f"{i}: {line}" for i, line in enumerate(lines))
    )
    payload = json.dumps({
        "messages": [{"role": "user", "content": prompt}],
        "temperature": 0
    }).encode()
    req = urllib.request.Request(MODEL_URL, data=payload, headers={
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json"
    })
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())["choices"][0]["message"]["content"]
Enter fullscreen mode Exit fullscreen mode

Temperature zero. I want a sorter, not a poet.

Step 3: Parse the response

This is where it gets tricky. The model sometimes wraps JSON in markdown fences.

def parse_response(text):
    text = text.strip()
    if text.startswith("```

"):
        text = text.split("\n", 1)[1].rsplit("

```", 1)[0]
    return json.loads(text)
Enter fullscreen mode Exit fullscreen mode

Three lines that saved me an hour of debugging.

Deploy: one cron line

0 * * * * cd /opt/log-triage && python3 triage.py >> triage.log 2>&1
Enter fullscreen mode Exit fullscreen mode

That's the entire deployment. The free server runs this every hour. I don't think about it until the alert fires.

The experiment

I ran the pipeline against 10,000 real log lines from three weeks of my side project.

The model found 47 MemoryError lines I had flagged as "investigate later" and forgotten. It found 12 silent database connection drops. It correctly identified 318 lines of framework noise.

The numbers

Category Model said Human review said Agreement
Critical 59 54 91%
Warning 312 298 95%
Noise 9,629 9,648 99%

The model missed 5 critical lines. It flagged 7 false positives.

One miss hurt. A database migration failed at 3 AM. The log line said FATAL: relation does not exist. The model called it warning.

Why? Because it was surrounded by 40 lines of "retrying in 5 seconds" noise. The model saw a pattern and assumed it was routine.

The fix

I added a rule layer on top of the model output.

HARD_FAILURES = ["FATAL", "PANIC", "relation does not exist", "Segmentation fault"]

def escalate(line, level):
    if any(token in line for token in HARD_FAILURES):
        return "critical"
    return level
Enter fullscreen mode Exit fullscreen mode

Rules catch what the model misses. The model catches what rules can't express.

Response time

The model took 8 to 15 seconds per 200-line batch. That's fine for an hourly job. It would be terrible for real-time log streaming.

Who should not use this

Teams with on-call rotations. If a human is already reading logs, this pipeline is overhead.

Anyone who needs deterministic severity. A model with temperature: 0 is still not a rule engine.

People with strict data residency rules. Logs are data. Sending them to a third-party endpoint has legal implications.

The cost

Zero. The free server runs the cron job. The free model endpoint processes the batches. The 10 million token allowance covered the entire experiment and then some.

I spent two hours writing the script and one hour reviewing the output. That's three hours to never read a raw log file again.

What I'd do differently

I'd add a deduplication step. The same error repeated 40 times should produce one alert, not forty.

I'd also add a weekly digest. The model summarizes the week's critical events into a five-line report. That's the next iteration.

Have you built a log triage pipeline? What did your model miss that a regex would have caught?

Top comments (0)