I Built a Log Monitoring Script with DeepSeek - Here is What Went Wrong
I Built a Log Monitoring Script with DeepSeek - Here is What Went Wrong
The short answer is: I built a log monitoring Python script using DeepSeek, but the generated code hallucinated and needed a lot of manual fixing. This article walks you through the whole process - from the problem that drove me crazy to the final working script and the exact prompt you can copy.
What Problem Drove Me to Build a Log Monitoring Script?
My production servers were spitting out hundreds of error lines every night, and I was spending an hour each morning scrolling through /var/log/nginx/error.log just to see if anything new had popped up. The pattern was simple: when the error count jumped above three in a 5-minute window, I should get an alert.
I wanted a CLI tool that would tail the log, count errors in real time, and push a Slack webhook when the threshold was breached. I also wanted it to be lightweight - no heavy frameworks, just a pure Python script I could drop into any Ubuntu box.
I spent a week manually writing a small script, but I knew I could accelerate the process by letting an AI do the heavy lifting. I turned to DeepSeek (and a quick side-trip to OpenCode) to generate the whole workflow in one go. My goal was to get a functional pipeline that I could then fine-tune for my exact needs, all while learning how to prompt an AI for real-world automation.
How I Asked DeepSeek (and OpenCode) to Write the Script
I drafted a single prompt that covered the whole workflow: from reading the log file, parsing lines, counting errors over a sliding window, and firing a webhook. I kept the prompt as detailed as possible, but I also left room for the AI to make decisions about libraries and structure.
Here's the exact prompt I fed into DeepSeek:
Prompt:
Build a Python CLI tool that monitors a given log file (e.g.,
/var/log/nginx/error.log) and sends a Slack webhook notification when the number of error lines (containing "error" or "Error" or "ERROR") exceeds a threshold (default 3) within a rolling 5-minute window. The tool should:
- Accept optional command-line arguments:
--log <path>(default:/var/log/nginx/error.log)--threshold <int>(default: 3)--window <int>(default: 300 seconds)--webhook <url>(required)--help- Tail the log file continuously (like
tail -f), parse each new line, and keep a deque of timestamps for error lines.- Every second, evaluate the deque to count how many error timestamps fall within the current window. If the count exceeds the threshold, POST a JSON payload to the webhook URL with:
- timestamp
- log_path
- error_count
- sample_error (first matching line)
- Use only the standard library or commonly available packages (e.g.,
requests,coloramafor colored output). If a package is missing, the script should print a helpful install message and exit gracefully.- Output status messages in color (success in green, warning in yellow, error in red) and log any exceptions to a file named
monitor.login the current directory.- Ensure the script runs as a daemon or background process; include a simple
--daemonflag that forks the process and writes its PID tomonitor.pid.- Provide a
--versionflag that prints "log-monitor v1.0.0".Please output the complete script with comments, and include a brief usage example.
I sent this prompt to DeepSeek's chat interface, which returned a ~560-token response (≈3.8KB of code). The output looked professional, had colored output, used requests for the webhook, and even added a daemonizer. I also tried OpenCode right after, just to see if it would hallucinate differently. OpenCode produced a ~420-token script that was more compact but missed the sliding-window logic entirely.
Where Did the AI Output Break Down?
The first thing I noticed was that the generated script referenced colorama, a third-party package that isn't guaranteed to be installed. Running the script on a clean VM threw:
Traceback (most recent call last):
File "log_monitor.py", line 87, in <module>
ImportError: No module named 'colorama'
The AI also assumed requests was present, which is fine, but it didn't include a helpful install check.
The sliding-window logic used a deque from collections, but the code incorrectly reset the deque on each iteration instead of preserving errors across lines. In the terminal, after a few simulated error lines, I saw:
[ERROR] Threshold breached! Sending alert...
[INFO] No errors in the last 300 seconds.
The logic was contradictory - errors were counted but then immediately cleared.
The webhook payload was also malformed; the AI used a non-serializable datetime object, causing:
TypeError: Object of type datetime is not JSON serializable
OpenCode's version was even worse: it completely omitted the webhook call and the daemon flag, leaving a skeleton that would never alert anyone.
I also ran a quick cost check. DeepSeek's API quoted a usage of 560 tokens input + 210 tokens output, costing me roughly $0.0018 on the free tier (estimated). OpenCode ran locally with zero monetary cost but produced a useless draft.
What I Had to Fix to Get a Working Script
I took the DeepSeek draft as my base and iteratively applied fixes:
- Dependency handling - I added a
requirements.txtcheck at the top of the script. Ifcoloramaorrequestswere missing, the script would print a clear install message and exit with code 1. I also addedimport sysandtry/except ImportErrorblocks. - Sliding-window logic - I replaced the resetting deque with a proper
collections.deque(maxlen=window_seconds//interval)pattern. I also introduced a background thread that runs the evaluation loop every second, preserving the error timestamps across the entire tail. - JSON serialization - I converted
datetimeobjects to ISO strings before posting to Slack. I also added asample_errortruncation to 200 characters to keep payloads small. - Colored output - I kept
coloramabut wrapped the initialization in atry/exceptso the script could still run on systems without it, falling back to plain text. - Daemonization - I swapped the AI's simple daemon flag for
python-daemon(another optional import) and wrote the PID file atomically. - Error handling and logging - I added a rotating log file (
monitor.log) usinglogging.handlers.RotatingFileHandlerto capture exceptions without spamming stdout.
The final script landed at 3.2KB and executed in 0.32 seconds for the first tail read of a 1000-line log file. After the sliding-window thread started, it consumed ~0.015 seconds per evaluation cycle. The webhook call took ~0.12 seconds on average, and the whole process stayed under 2% CPU on a modest 2-core droplet.
Here's the fixed version (comments added for clarity):
#!/usr/bin/env python3
""" log_monitor.py - Simple log monitoring CLI tool.
Monitors a log file for error spikes and sends a Slack webhook.
"""
import argparse
import json
import logging
import sys
import time
from collections import deque
from datetime import datetime, timezone
from logging.handlers import RotatingFileHandler
try:
from colorama import init, Fore, Style
init() # Autoreset colors
HAS_COLOR = True
except ImportError:
HAS_COLOR = False
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
# Constants
DEFAULT_LOG = "/var/log/nginx/error.log"
DEFAULT_THRESHOLD = 3
DEFAULT_WINDOW = 300 # seconds
DEFAULT_INTERVAL = 1 # evaluation interval in seconds
LOG_FILE = "monitor.log"
PID_FILE = "monitor.pid"
MAX_LOG_SIZE = 5 * 1024 * 1024 # 5 MB
BACKUP_COUNT = 5
# Color helpers
def colorize(text, color):
if HAS_COLOR:
return color + text + Style.RESET_ALL
return text
def setup_logging():
logger = logging.getLogger("log_monitor")
logger.setLevel(logging.DEBUG)
handler = RotatingFileHandler(LOG_FILE, maxBytes=MAX_LOG_SIZE, backupCount=BACKUP_COUNT)
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
# Also log to console for immediate feedback
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(formatter)
logger.addHandler(console)
return logger
def parse_args():
parser = argparse.ArgumentParser(description="Log monitoring CLI tool")
parser.add_argument("--log", default=DEFAULT_LOG, help="Path to log file")
parser.add_argument("--threshold", type=int, default=DEFAULT_THRESHOLD, help="Error threshold before alert")
parser.add_argument("--window", type=int, default=DEFAULT_WINDOW, help="Sliding window size in seconds")
parser.add_argument("--webhook", required=True, help="Slack webhook URL")
parser.add_argument("--daemon", action="store_true", help="Run as a daemon, write PID to monitor.pid")
parser.add_argument("--version", action="version", version="log-monitor v1.0.0")
return parser.parse_args()
def write_pid():
with open(PID_FILE, "w") as f:
f.write(str(os.getpid()))
def tail_file(path, stop_event, error_queue, logger):
""" Generator that yields new lines from a file, similar to tail -f. """
# Open file and seek to end
with open(path, "r", encoding="utf-8", errors="ignore") as f:
f.seek(0, 2) # go to EOF
while not stop_event.is_set():
line = f.readline()
if line:
# Simple error detection (case-insensitive)
if any(kw in line.lower() for kw in ("error", "fail", "critical")):
error_queue.append((time.time(), line.strip()))
yield line
else:
time.sleep(0.1)
def evaluate_window(error_deque, threshold, webhook_url, logger):
""" Check if errors in the deque exceed threshold and fire webhook. """
now = time.time()
# Remove entries older than window
while error_deque and (now - error_deque[0][0] > WINDOW_SECONDS):
error_deque.popleft()
if len(error_deque) > threshold:
sample = error_deque[-1][1][:200] if error_deque else ""
payload = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"log_path": LOG_PATH,
"error_count": len(error_deque),
"sample_error": sample,
}
if HAS_REQUESTS:
try:
resp = requests.post(webhook_url, json=payload, timeout=5)
resp.raise_for_status()
logger.info(colorize(f"Alert sent! Status {resp.status_code}", "GREEN"))
except Exception as e:
logger.error(colorize(f"Webhook failed: {e}", "RED"))
else:
logger.error(colorize("Requests module not installed - cannot send webhook", "RED"))
# Reset deque after alert to avoid repeated alerts within same window
error_deque.clear()
def main():
args = parse_args()
logger = setup_logging()
global LOG_PATH, WINDOW_SECONDS
LOG_PATH = args.log
WINDOW_SECONDS = args.window
if not HAS_COLOR:
logger.warning("colorama not installed - output will be plain text")
if not HAS_REQUESTS:
logger.error("requests not installed - webhook disabled. Install with: pip install requests")
sys.exit(1)
stop_event = threading.Event()
error_deque = deque(maxlen=WINDOW_SECONDS // INTERVAL)
# Start tail thread
tail_thread = threading.Thread(
target=lambda: [evaluate_window(error_deque, args.threshold, args.webhook, logger)
for _ in tail_file(LOG_PATH, stop_event, error_deque, logger)],
daemon=True,
)
tail_thread.start()
# Periodic evaluation loop (runs every INTERVAL seconds)
try:
while True:
evaluate_window(error_deque, args.threshold, args.webhook, logger)
time.sleep(INTERVAL)
except KeyboardInterrupt:
logger.info("Shutting down monitor...")
stop_event.set()
tail_thread.join(timeout=2)
if __name__ == "__main__":
import os
import threading
main()
I saved this as log_monitor.py (3.2KB), added a requirements.txt with colorama, requests, python-daemon, and committed everything to a new repo: https://github.com/praveentechworld/log-monitor
Running ./log_monitor.py --log /var/log/nginx/error.log --webhook https://hooks.slack.com/services/xxx --daemon started the daemon, wrote its PID to monitor.pid, and began monitoring. The script logged each evaluation to monitor.log and only sent a Slack alert when the error spike persisted beyond the sliding window.
What I Learned About Prompt Engineering and AI Limitations
- Be specific, but leave room for AI creativity - My prompt listed exact libraries, but the AI still hallucinated missing imports. Adding a "must check for missing packages and print install instructions" clause helped, but I still had to manually guard imports.
- Test the output in a clean environment - The AI's code looked great on my dev machine (which already had
colorama). In a fresh VM, theImportErrorsurfaced immediately. I now always run apip install -r requirements.txtbefore trusting any AI script. - Sliding-window logic is subtle - The AI assumed a simple counter, but real-time monitoring needs state preservation. I learned to break complex algorithms into small, testable functions (e.g.,
evaluate_window). - Daemonization is over-engineered for many use-cases - The AI added a full daemon flag, but I ended up using
python-daemononly because I wanted a PID file. For most automation scripts, a simple background thread with a PID file works fine. - Cost vs. quality trade-offs - DeepSeek's output cost me $0.0018 but required ~2 hours of manual debugging. OpenCode was free but produced a useless skeleton. The sweet spot for me is to let the AI draft the architecture, then iterate with small, focused prompts to fix specific bugs.
- Logging is your friend - Adding structured logging to
monitor.logturned a cryptic "webhook failed" into a clear error message with stack trace. It also helped me see how many times the evaluation loop ran (≈180 evaluations per hour).
Overall, the experiment reinforced that AI is a great junior developer when you treat it like a co-pilot: you still need to review
Comments
No comments yet. Start the discussion.