Parsing Logs & Text
Ops scripts extract signals from log files and command output using streaming I/O, regex, and optional JSON parsing - without loading multi-gigabyte files into memory.
Search across all documentation pages
Ops scripts extract signals from log files and command output using streaming I/O, regex, and optional JSON parsing - without loading multi-gigabyte files into memory.
import re
from pathlib import Path
PATTERN = re.compile(r"ERROR (?P<component>\w+): (?P<message>.+)")
for line in Path("app.log").open(encoding="utf-8", errors="replace"):
match = PATTERN.search(line)
if match:
print(match.groupdict())When to reach for this:
Stream nginx access log, aggregate status codes, handle JSON lines mixed format.
import json
import re
from collections import Counter
from dataclasses import dataclass
from pathlib import Path
NGINX = re.compile(
r'^(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] "(?P<method>\w+) (?P<path>\S+)'
)
@dataclass
class ParseResult:
status_counts: Counter
errors: int
def parse_access_log(path: Path) -> ParseResult:
counts: Counter = Counter()
errors = 0
with path.open(encoding="utf-8", errors="replace") as f:
for line in f:
line = line.strip()
if not line:
continue
if line.startswith("{"):
try:
rec = json.loads(line)
counts[rec.get("status", "unknown")] += 1
continue
except json.JSONDecodeError:
errors += 1
continue
m = NGINX.search(line)
if not m:
errors += 1
continue
# status code often after request - simplified demo uses path token
counts["parsed"] += 1
return ParseResult(counts, errors)
if __name__ == "__main__":
result = parse_access_log(Path("access.log"))
print(result.status_counts)
print("parse errors", result.errors)What this demonstrates:
errors="replace" avoids crash on invalid UTF-8 bytes(?P<name>...) for readable extractionimport gzip
from pathlib import Path
with gzip.open(Path("app.log.gz"), "rt", encoding="utf-8", errors="replace") as f:
for line in f:
....read() - OOM on GB logs. Fix: iterate lines or use mmap for advanced cases.datetime.timezone.| Alternative | Use When | Don't Use When |
|---|---|---|
rg/grep shell | Quick human inspection | Need aggregation logic in Python |
| ELK/Loki | Centralized log platform exists | One-off laptop analysis |
pandas.read_csv | Tabular logs fit in RAM | Multi-GB files |
split on delimiters when format is fixed; regex when fields vary or optional.
State machine: start record on timestamp line, accumulate until next timestamp pattern.
Open .gz with gzip.open in text mode; process rolled files in sorted order by mtime.
json.loads per line (NDJSON) - fastest path when apps emit JSON logs.
Fixture files with golden expected counts in pytest parametrize.
Line iteration is usually enough; consider multiprocessing per file when batching thousands of files.
Stay in binary mode and decode only known text regions - do not assume UTF-8 throughout.
datetime.fromisoformat for JSON logs; strptime for legacy formats with explicit format string.
Pass path to gzip.open - pathlib objects work where stdlib accepts pathlike.
When ad-hoc script stabilizes - promote to scheduled job with tests and monitoring on parse error rate.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026