commit da8b86b258d774cc5f0d8b9d366c7ac6a2b66728 Author: disqualifier Date: Sat Jun 27 20:21:02 2026 -0400 init: stdlib app-entry-point logging setup (live run.log, rotation, gzip, retention) Signed-off-by: disqualifier diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6ff830 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# claude +CLAUDE.md + +# python +__pycache__/ +*.py[cod] +*.egg-info/ +dist/ +build/ +.eggs/ + +# env +.venv/ +venv/ +.env +.pytest_cache/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d3078e --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# log_setup + +Stdlib, sync, **zero-dependency** logging setup an application calls **once** at its +entry point: a live `run.log`, rotation (daily / size / on-start), gzip of rolled +files, retention, console output, and a consistent `time | module | level | message` +format. + +It **configures** logging (handlers, rotation, format) — which reusable libraries here +must never do. That's fine because `log_setup` is the *application's* entry-point +setup, not library-internal config. Libraries still only `logging.getLogger(__name__)` +and emit; their records flow into the handlers `log_setup` wired. + +## Install + +``` +log_setup @ git+ssh://git@git.rethinkstudios.io/rethink-public/log_setup.git@v0.1.0 +``` + +No dependencies — stdlib only. + +## Quick start + +```python +import logging +from log_setup import setup_logging + +setup_logging(name="run", level="INFO") # daily rotation, logs/ dir, gzip (file only) + +log = logging.getLogger(__name__) +log.info("started") # -> ./run.log (add console=True for stdout too) +``` + +Call it once, at the app's entry point — before the rest of the app runs. Every module +(yours and the libraries you import) then just does `logging.getLogger(__name__)` and +emits; the records land in the configured root. + +## What you get + +- **Live file** at a stable path: `./run.log` — always `tail -f run.log`, no dated name + to chase. Rolled/compressed copies go into `log_dir` (default `logs/`). +- **Format:** `2026-06-27 19:55:05 | module.name | INFO | message`. `%(name)s` is the + `getLogger` name each module used, so you see which lib/module logged. +- **Rotation** (`rotate=`): + - `"daily"` (default) — rolls at midnight, dated name into `log_dir`, keeps + `backup_count` days. + - `"size"` — rolls at `max_bytes`, numbered backups in `log_dir`. + - `"on_start"` — on startup, moves an existing `run.log` into `log_dir` + (`run..log[.gz]`) and starts fresh; prunes to `backup_count`. + - `None` — single file, no rotation. +- **compress=True** (default) gzips each rolled file (`run.log.2026-06-27.gz`). +- **Retention** = `backup_count` (default 14) for every mode. +- **console=True** (off by default) also logs to stdout in the same format — opt in when + you want live terminal output alongside the file. + +## Signature + +```python +setup_logging( + name="run", # base -> run.log (the live file at cwd) + log_dir="logs", # rotated/compressed copies live here (created if absent) + level="INFO", # root level (str name or logging constant) + rotate="daily", # "daily" | "size" | "on_start" | None + backup_count=14, # rotated files to keep (older auto-deleted) + max_bytes=10_000_000, # only for rotate="size" + compress=True, # gzip rolled files + console=False, # also log to stdout (off by default; opt in) + queue=False, # route through a background QueueListener (async-friendly) + fmt=None, # override the format string + datefmt=None, # override the date format +) -> logging.Logger # returns the configured root logger +``` + +## Async-friendly (`queue=True`) + +For async-heavy apps, `queue=True` routes records through a stdlib `QueueHandler` to a +background `QueueListener` that owns the file/console handlers, so the event loop never +blocks on file I/O. The API stays sync (`log.info()` as usual); the queue is internal. +The listener is stopped (and flushed) cleanly at process exit, so no records are lost. + +```python +setup_logging(name="run", queue=True) +``` + +## Safety + +- **Idempotent:** calling `setup_logging` again clears only the handlers it added (no + duplicate lines) and leaves handlers your app added itself alone. +- **Never crashes the app over logging:** if `log_dir` isn't writable, it falls back to + console-only with a warning instead of raising. + +## Scope — what this is NOT + +`log_setup` produces clean, rotating, compressed, retention-managed, consistently +formatted **files**. It does **not** ship logs anywhere — no Loki/ELK/syslog/network +handlers. Getting files to a backend is a separate concern (e.g. Promtail tails +`run.log` → Loki → Grafana panels + alerting). Keeping shipping out means the log +backend can change without touching any app, and the consistent format here is what +makes downstream parsing and alerting easy. + +Also out of v0.1.0 (possible later additions): structured/JSON logging, color +formatting, per-logger filters, remote handlers. + +## Versioning + +Tagged `vX.Y.Z`. Pin the tag.