Structured event logging to SQLite, in one file. Zero dependencies · thread-safe · deduplicated · separates business ALERTs from technical ERRORs.
A queryable event log without a logging framework, a server, or any setup. Drop
event_logger.py into your project and you get a thread-safe, deduplicated,
SQLite-backed log you can query with plain SQL.
from event_logger import log_event, read_events
log_event("billing", "invoice_sent", details={"invoice": 42})
log_event("billing", "payment_late", level="ALERT", details={"days_overdue": 5})
for e in read_events():
print(e["ts"], e["automation"], e["level"], e["event"])⭐ If it's useful, a star helps.
print() disappears, log files rot, and a full logging stack is overkill for a
script or a small fleet of automations. This gives you the 20% that matters:
- 🗃️ SQLite-backed — query your events with SQL, no extra service.
- 🚦 ALERT vs ERROR — a first-class distinction (see below) so a dashboard can show real failures without drowning in business noise.
- 🧷 Deduplicated — identical events collapse to one row (SHA-1 id +
INSERT OR IGNORE). - 🧵 Thread-safe — WAL mode + a lock; safe from multiple threads.
- 💥 Crash hook —
install_crash_hook()writes any uncaught exception (and thread exceptions) straight to the log — works underpythonwtoo. - 🪶 Zero dependencies — Python 3.8+ standard library only.
The single design decision that makes the log useful:
| Level | Meaning |
|---|---|
INFO / DEBUG |
normal execution / diagnostics |
WARN |
a handled anomaly, worth watching |
ALERT |
a legitimate business alert — a threshold exceeded, an SLA breached, an expected-but-bad outcome. Not a bug. |
ERROR / CRITICAL |
a technical failure / crash |
Filtering ALERT from ERROR lets you route "the system is broken" separately
from "the business needs attention".
It's one file — copy event_logger.py into your project.
from event_logger import log_event, read_events, configure
configure("events.db") # optional; defaults to ./events.db (or $EVENT_LOG_DB)
log_event("worker", "started")
log_event("worker", "job_failed", level="ERROR", details={"job": "sync"})
recent = read_events(limit=20) # list of dicts, oldest firstRun the demo:
python examples/demo.pyconfigure(db_path) # set the SQLite path (before first log)
log_event(automation, event, level="INFO", details=None)
append_record(record) / append_records(records) # log raw dict record(s)
read_events(limit=0) # list[dict], oldest first
install_crash_hook(automation_name) # auto-log uncaught exceptionsid | ts | automation | level | event | message | source_file | source_line | source_kind | details(JSON)
Query it like any SQLite DB:
SELECT ts, automation, event FROM events WHERE level = 'ALERT' ORDER BY ts DESC;python -m unittest discover testsMIT — see LICENSE.
Part of a small Windows-automation toolkit · companion project: win-keepalive (keep Python scripts alive 24/7 on Windows).