Stop Drowning in Logs: Build Your Own Python SIEM in Under 100 Lines
You’ve deployed your application. The servers are humming. But beneath the surface, a firehose of data is blasting your system: firewall drops, 404 errors, user logins, database queries. It’s a chaotic stream of text that holds the difference between a normal Tuesday and a catastrophic breach.
Most developers treat logs as an afterthought—files to be grepped only when something breaks. But in cybersecurity, logs are the single source of truth. The challenge? They are wildly inconsistent. A firewall log looks nothing like a web server access log. Manually correlating these events is impossible at scale.
This is where a Security Information and Event Management (SIEM) system comes in. Commercial SIEMs are massive, expensive beasts. But the core principles of ingesting, normalizing, and correlating data can be mastered with Python.
This guide is the first step in our final project: building a lightweight, functional SIEM. We aren't just writing code; we are building a brain for your network. We will start with the absolute foundation: Log Normalization.
The Problem: Translating the Chaos
Imagine a detective investigating a crime where witnesses speak different languages. To find the truth, they need a translator to standardize the testimonies into a single, coherent narrative.
In a SIEM, raw logs are the witnesses. We need a "Translator" (the Normalizer) to convert them into a standardized format. Without this, correlation is impossible. You can't ask, "Show me all failed logins from this IP," if the firewall calls it src_ip and the web server calls it client_address.
The Solution: The Normalization Pipeline
Our Python SIEM will follow a strict pipeline: 1. Ingestion: Read the raw data. 2. Normalization: Parse and standardize the data into a dictionary. 3. Storage: Save it to a database. 4. Correlation: Analyze patterns.
In this post, we focus on steps 1 and 2. We will write a robust parser that takes a messy Apache access log and turns it into a clean, structured Python object ready for analysis.
The Code: The "Translator" Script
We will use Python’s re (regular expression) module. It is the Swiss Army knife for cybersecurity professionals. We aren't just splitting strings; we are extracting intelligence.
Here is the core logic for our SIEM's ingestion engine.
import re
import sys
from datetime import datetime
from typing import Optional, Dict, Any
# 1. The Regex Pattern
# We use named capture groups (?P<name>...) for clean, readable data extraction.
# This adheres to the DRY (Don't Repeat Yourself) principle.
LOG_PATTERN = re.compile(
r'(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) ' # Capture IP Address
r'- - ' # Ignore standard placeholders
r'\[(?P<timestamp>[^\]]+)\] ' # Capture Timestamp
r'"(?P<method>\w+) (?P<path>[^\s]+) HTTP/\d\.\d" ' # Capture Method & Path
r'(?P<status>\d{3}) ' # Capture Status Code
r'(?P<size>\d+) ' # Capture Response Size
r'"(?P<referer>[^"]*)" ' # Capture Referer
r'"(?P<user_agent>[^"]*)"' # Capture User Agent
)
def parse_log_line(log_line: str) -> Optional[Dict[str, Any]]:
"""
Translates a raw Apache log line into a standardized SIEM event.
"""
match = LOG_PATTERN.match(log_line)
if match:
raw_data = match.groupdict()
# 2. Timestamp Normalization
# SIEMs rely on ISO 8601 format for accurate time-series correlation.
try:
dt_obj = datetime.strptime(raw_data['timestamp'], '%d/%b/%Y:%H:%M:%S %z')
normalized_time = dt_obj.isoformat()
except ValueError:
# In a real SIEM, we'd log this error to a dead-letter queue
return None
# 3. Schema Mapping
# We map arbitrary fields to our strict internal schema.
normalized_event = {
'event_type': 'HTTP_ACCESS',
'event_time': normalized_time,
'source_ip': raw_data['ip'],
'http_method': raw_data['method'],
'request_path': raw_data['path'],
'status_code': int(raw_data['status']), # Cast to int for math operations
'response_size_bytes': int(raw_data['size']),
'user_agent': raw_data['user_agent']
}
return normalized_event
return None
# --- Simulation ---
RAW_LOG = '192.168.1.10 - - [05/Sep/2024:10:30:00 +0000] "GET /admin HTTP/1.1" 401 1234 "-" "Mozilla/5.0"'
event = parse_log_line(RAW_LOG)
if event:
print("--- Normalized Event ---")
for k, v in event.items():
print(f"{k:<20}: {v}")
Why This Matters: The Engineering Behind the Code
This script looks simple, but it solves the three biggest hurdles in defensive engineering:
1. The Power of Named Capture Groups
Notice (?P<ip>...) in the regex? This is a Python feature that allows us to extract data into a dictionary with specific keys immediately. Without this, we would be referencing groups by fragile numbers (like group(1)), which breaks the moment a log format changes slightly. This is the DRY principle in action—writing resilient code that handles the "what" (the data) rather than the "where" (the position).
2. Time is Everything
Security analysis is useless without accurate timing. A brute-force attack might look like normal traffic if you ignore the sequence of events. By converting the Apache timestamp (05/Sep/2024...) to ISO 8601 (2024-09-05T10:30:00+0000), we ensure that our database can sort events chronologically, regardless of the original log source.
3. Schema Enforcement
The final dictionary (normalized_event) is our Common Schema. It acts as the universal language for the rest of our SIEM.
* Correlation: Later, we can write rules like if event['status_code'] == 401 and event['event_type'] == 'HTTP_ACCESS'.
* Storage: This dictionary maps directly to a SQL table or a NoSQL document. If we didn't do this, our database would be a mess of unsearchable text blobs.
Conclusion: Building the Foundation
We have successfully built the "Translator." We can take a line of text that looks like garbage to a human and turn it into structured intelligence that a computer can analyze.
But a SIEM is more than just parsing. It needs to store this data and, crucially, correlate it. In the next part of this series, we will hook this parser into a database and write our first correlation rule to detect a classic attack pattern: a Port Scan followed by a Brute Force attempt.
Until then, look at your logs. They aren't just text files; they are a story waiting to be read.
Let's Discuss
- In your experience, what is the "ugliest" log format you've had to parse, and how did you handle the inconsistencies?
- Do you think lightweight Python SIEMs are viable for small startups, or is the maintenance overhead too high compared to cloud-native solutions like Splunk or Datadog?
The concepts and code demonstrated here are drawn directly from the comprehensive roadmap laid out in the book Python Defensive Cybersecurity Amazon Link of the Python Programming Series, you can find it also on Leanpub.com.
Code License: All code examples are released under the MIT License. Github repo.
Content Copyright: Copyright © 2026 Edgar Milvus | Privacy & Cookie Policy. All rights reserved.
All textual explanations, original diagrams, and illustrations are the intellectual property of the author. To support the maintenance of this site via AdSense, please read this content exclusively online. Copying, redistribution, or reproduction is strictly prohibited.