← Back to Blog
Code Snippets July 25, 2026

Parse Unstructured Log Lines into Structured Data with Named-Capture Regex (Python)

🔧 Code Snippet of the Day — July 25, 2026

Parse Unstructured Log Lines into Structured Data with Named-Capture Regex (Python)

Stop using a dozen fragile split()/strip()/index() calls. Use named capture groups for clean, extensible log parsing:

import re
from datetime import datetime

NGINX = re.compile(
    r'^(?P<ip>\d+\.\d+\.\d+\.\d+)\s+'
    r'\[(?P<timestamp>[^\]]+)\]\s+'
    r'"(?P<method>\w+)\s+(?P<path>\S+)\s+[^"]+"\s+'
    r'(?P<status>\d{3})\s+(?P<bytes>\d+|-)'
)

def parse_line(line):
    m = NGINX.match(line.strip())
    if not m:
        return None
    r = m.groupdict()
    r["status"] = int(r["status"])
    r["bytes"] = 0 if r["bytes"] == "-" else int(r["bytes"])
    try:
        r["timestamp"] = datetime.strptime(
            r["timestamp"], "%d/%b/%Y:%H:%M:%S %z"
        )
    except ValueError:
        pass
    return r


Multi-format registry pattern — try nginx → Apache → app log, first match wins:
LOG_PARSERS = [
    (NGINX_REGEX, nginx_coerce),
    (APACHE_REGEX, apache_coerce),
    (APP_LOG_REGEX, app_coerce),
]

def parse_line(line):
    for pattern, coerce in LOG_PARSERS:
        m = pattern.match(line.strip())
        if m:
            return coerce(m.groupdict())
    return None


Streaming file parser — 10GB logs, ~1MB RAM:
def parse_log_file(path):
    with open(path) as f:
        for line in f:
            r = parse_line(line)
            if r:
                yield r


💡 Adding a new log format is one line: LOG_PARSERS.append((regex, coerce_fn)). Pipe to csv.DictWriter for analysis, or stream into SQLite for querying.