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")
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"]
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)
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
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
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)