Files

5.1 KiB

type, title, description, tags, timestamp
type title description tags timestamp
architecture Logging & Run Summary Structured JSON logging in 10Backward — JsonFormatter, secret redaction, and the startup / error / run_complete event helpers.
logging
observability
json
2026-08-04T17:51:00Z

Purpose

tenbackward.logging_setup replaces the scaffold-era basicConfig text logger with a single-line JSON formatter that downstream log shippers can index. It also owns the three helpers the runner uses to record structured events: log_startup, log_error, log_run_summary.

JsonFormatter

JsonFormatter.format(record) returns one JSON object per log record:

Key Source
ts UTC datetime.fromtimestamp(record.created).isoformat().
level record.levelname (e.g. INFO, ERROR).
logger record.name (always tenbackward).
message record.getMessage().
extras Any keyword passed via logging.info(..., extra={...}) that is not a reserved LogRecord attribute and whose key is not in _SECRET_KEYS.
exc_type When record.exc_info is set, the exception class name.

Reserved LogRecord attributes and any key starting with _ are dropped before serialization, so the payload contains only the values that callers intentionally attached.

Secret Redaction

_SECRET_KEYS is the canonical denylist applied in two places:

  1. At format time for keys passed via extra={...}.
  2. At helper time by _redact_dict(...) so values reaching log_startup / log_error never expose known-sensitive fields even before they hit the formatter.

Deny-list contents (case-insensitive):

token, access_token, mastodon_access_token, password, secret,
authorization, api_key

Any matching key is replaced with the string [REDACTED].

Helper Functions

Helper When it fires Logger / Level event field
configure_json_logging(level=INFO) First line of main.main(). Idempotent — clears existing handlers. n/a n/a
log_startup(**fields) After config load + data_dir.mkdir, before the pipeline pass. INFO startup
log_error(event, *, exc=None, **fields) When a pipeline attempt raises, or when the retry budget is exhausted. ERROR the supplied event name; exc_type added when exc is provided.
log_run_summary(scanned, matched, posted, skipped, posted_ids) Once per successful pass. Silently skipped when all counters are zero and no ids were posted. INFO run_complete

Startup payload

log_startup is invoked with:

version, site_url, run_at, tz, hashtags, throwback_prefix

Each is added to the startup event payload under its own key.

Run-summary payload

log_run_summary is the canonical "what did the bot do today?" record:

{
  "ts": "2026-08-04T09:00:00+00:00",
  "level": "INFO",
  "logger": "tenbackward",
  "message": "run complete",
  "event": "run_complete",
  "scanned": 0,
  "matched": 0,
  "posted": 0,
  "skipped": 0,
  "posted_ids": []
}

A pass with no candidates emits no line at all, matching the "silent on no matches" contract.

Examples

Successful run with one new post

{"ts":"2026-08-04T09:00:00+00:00","level":"INFO","logger":"tenbackward","message":"startup","event":"startup","version":"0.1.0","site_url":"https://blog.example.com","run_at":"09:00","tz":"Europe/Berlin","hashtags":"#throwback,#10backward","throwback_prefix":"Heute vor 10 Jahren:"}
{"ts":"2026-08-04T09:00:01+00:00","level":"INFO","logger":"tenbackward","message":"run complete","event":"run_complete","scanned":1,"matched":1,"posted":1,"skipped":0,"posted_ids":["2025-08-04-post-slug"]}

Retry budget exhausted

{"ts":"...","level":"ERROR","logger":"tenbackward","message":"pipeline_error","event":"pipeline_error","attempt":1,"max_attempts":4,"exc_type":"ConnectionError"}
{"ts":"...","level":"ERROR","logger":"tenbackward","message":"pipeline_error","event":"pipeline_error","attempt":2,"max_attempts":4,"exc_type":"ConnectionError"}
{"ts":"...","level":"ERROR","logger":"tenbackward","message":"pipeline_failed","event":"pipeline_failed","attempts":4,"exc_type":"ConnectionError"}

The container then exits with status 1.

Token accidentally logged

Even if a future call wrote extra={"access_token": "..."}, the formatter replaces it:

{ "...": "...", "access_token": "[REDACTED]" }

Related