Releases: collide-ai/collide-logging-py
Release list
v0.5.1 — Starlette exception-path request log
Mirrors the v0.5.0 Django exception-path fix onto the Starlette/pure-ASGI middleware (#44).
uv add "git+https://github.com/collide-ai/collide-logging-py.git@v0.5.1"Fixed
starlette.RequestLoggingMiddlewareskipped thehttp.requestline when the wrapped app raised (#44). The line was emitted afterawait self.app(...)returned, so an unhandled exception jumped past it into thefinallyand the genuinely-unhandled 500 produced no request log. The middleware now emits a status-500http.requestline aterrorlevel with the traceback (exc_info) before re-raising. No response exists on that path, soX-Request-IDis not set and the status is reported as 500 even ifhttp.response.startalready fired;request_idstill rides the bound contextvar. ASGI analogue of the Django exception-path fix shipped in v0.5.0.
Notes
- The streaming-duration (#41) and client-disconnect (#42) concerns do not apply to pure-ASGI:
await self.app(...)returns only after the body drains, so itsduration_msis already correct. CLAUDE.mdgains a "Request logging guarantees" section documenting the cross-adapter middleware contract, including the open #42 Django known limitation.
Full prior history: v0.5.0 (Django middleware overhaul) — see CHANGELOG.
v0.5.0 — Django middleware overhaul
Overhauls the Django RequestLoggingMiddleware: correct user attribution for email-auth services, native async support, accurate streaming durations, and a request log on the exception path. Bundles #34, #40, and #41.
uv add "git+https://github.com/collide-ai/collide-logging-py.git@v0.5.0"Fixed
user: nullfor email-auth users (#34). The middleware readuser.username, which isNoneon models withUSERNAME_FIELD = "email". It now readsuser.get_username(), correct for both default-username and email-auth models.duration_msmeasured at stream open for streaming responses (#41). Thehttp.requestline is now deferred by hookingresponse.close(), soduration_msreflects time to stream close. Covers WSGI (incl. abandonment, per PEP 3333), ASGI normal completion, and errors.- Unhandled handler exceptions produced no request log (#41). A status-500 line with the traceback is now emitted before the exception propagates (guarded so a throwing
request.usercan't mask the real error).
Added
RequestLoggingMiddlewareis now async-capable (#40). Declaressync_capable/async_capable, detects an asyncget_response, and awaits it on a coroutine path — no sync/async adaptation boundary on ASGI stacks. Sync deployments unchanged.
Known limitation
- On ASGI, a client disconnect mid-stream cancels the request task without calling
response.close()(Django's ASGIHandler), so that one case is not logged (#42). All WSGI, all non-streaming, and ASGI streams that complete normally or error are covered.
Compatibility
django>=5. All APIs used are available on Django 5.0+; CI exercises the resolved Django (6.0.4) only.
Full prior history: v0.4.1 (validate-mode fail-safe), v0.4.0 (events API safety) — see CHANGELOG.
v0.4.1 — validate-mode fail-safe
Hardens validation-mode resolution so a misconfigured prod environment cannot start crashing the host.
uv add "git+https://github.com/collide-ai/collide-logging-py.git@v0.4.1"Fixed
- Unrecognized
COLLIDE_LOG_VALIDATEvalues fail safe tolenient(#37). Previously only the literal"lenient"enabled lenient mode; any other set value (a typo likeleniant, stray whitespace, or empty string) silently fell through toraisemode, where a schema violation throwsEventValidationErrorinto the host — defeating v0.4.0's "never crashes in prod" guarantee. The var is now normalized (whitespace stripped, case-insensitive); unrecognized set values resolve tolenientwith a one-timecollide_logging.invalid_validate_modewarning carrying the offending value.
Behavior note: COLLIDE_LOG_VALIDATE="" now resolves to lenient (was raise-mode). Leave the var unset for raise-mode.
v0.4.0 — events API safety
Makes the typed-events API safe to recommend for high-value and error-path events. Fully additive at call sites; one prod-mode behavior change (lenient mode no longer drops events).
uv add "git+https://github.com/collide-ai/collide-logging-py.git@v0.4.0"Fixed
- #36 — lenient mode no longer discards the offending event. Under
COLLIDE_LOG_VALIDATE=lenienta violation previously dropped the whole payload and emitted only a meta-event — events vanished during incidents. Now the event is emitted best-effort under its real name (unknown fields dropped, known fields redacted, a_schema_violationmarker added), with thecollide_logging.schema_violationmeta-event alongside as an alertable signal.raisemode unchanged.
Added
- #33 —
CollideLogger.event()acceptslevel=andexc_info=. Schema-validated error-path events can emit atwarning/error/etc. and carry a traceback. Defaults leave the happy-path record byte-identical. - #35 — public
digest_value(). Exposes the{"len", "sha256"}digest behindFieldSpec(redact=True)for hand-curated redaction of sensitive free-text on the plainlog.info(...)path. Auto-redaction remains name-based only.
Upgrading
No call-site changes required. Services on lenient should confirm an alert on event="collide_logging.schema_violation". Avoid declaring an event field named _schema_violation — now reserved.
Follow-up: #37 (unrecognized COLLIDE_LOG_VALIDATE values fall back to raise-mode).
v0.3.0
Bridges foreign stdlib loggers (Django's django.request, gunicorn, third-party libraries) through the structlog processor chain. Every line on stdout is now valid collide/v1 JSON — not just lines that originate from a CollideLogger.
Install
uv add "git+https://github.com/collide-ai/collide-logging-py.git@v0.3.0"What changed
configure()now installs astructlog.stdlib.ProcessorFormatter-driven handler. Foreign stdlib records receive the same timestamp / level / service / logger fields and redaction pass as structlog-originated records.extra={}fields on foreign stdlib calls are merged into the event dict before redaction runs.- Exception info on foreign records (
exc_info=True) is formatted into theexceptionfield. - Existing
CollideLoggeroutput shape is unchanged.
Upgrading
No changes required at call sites. Services silencing django.request or other noisy loggers to avoid non-JSON stdout can remove those workarounds — the records will now appear as JSON instead.
See CHANGELOG for full details.
v0.2.0
Adds a public events API so adapter authors (the first is collide-logging-hermes) can declare event schemas and emit validated records without reaching into private internals. Fully additive — every v0.1.0 call site keeps working.
Install via tag:
uv add "git+https://github.com/collide-ai/collide-logging-py.git@v0.2.0"Public API
collide_logging.FieldSpec(type, required=False, redact=False)— one field on an event schema.collide_logging.EventSchema(name, fields, description="")— one named event type.collide_logging.register_event_schema(schema)— idempotent on identical re-registration; raisesValueErroron a conflicting redefinition.collide_logging.list_schemas()— returns registered schemas sorted by name.collide_logging.EventValidationError— raised on schema violations in raise mode.collide_logging.CollideLogger—BoundLoggersubclass returned byget_logger(). Addsevent(name, **fields)that validates the call, redacts flagged fields, and emits through the standard processor chain.
Behavior
- Validation mode is controlled by
COLLIDE_LOG_VALIDATE. Unset orraise(dev default): unknown event names, missing required fields, and unknown field keys raiseEventValidationError.lenient(prod): drops the offending record and emits acollide_logging.schema_violationmeta-event instead. Never crashes the host process. - Fields flagged
redact=Trueare replaced with{"len": <bytes>, "sha256": "<first 8 hex>"}before emission. Non-str/bytesvalues are coerced throughrepr()first. Global suffix-based redaction (*_token, etc.) still applies on top, unchanged from v0.1.0.
v0.1.0
Initial release for internal consumption. Implements the collide/v1 logging spec.
Install
uv add "git+https://github.com/collide-ai/collide-logging-py.git@v0.1.0"
uv add "collide-logging[django] @ git+https://github.com/collide-ai/collide-logging-py.git@v0.1.0"
uv add "collide-logging[fastapi] @ git+https://github.com/collide-ai/collide-logging-py.git@v0.1.0"
uv add "collide-logging[flask] @ git+https://github.com/collide-ai/collide-logging-py.git@v0.1.0"Public API
collide_logging.configure(service, *, level, json, extra_redact_keys)— wires structlog and stdlib logging to emitcollide/v1-conformant JSON (or a developer-friendly console renderer for TTYs).collide_logging.get_logger(name)— thin wrapper overstructlog.get_logger.collide_logging.bind_worker_run_id(run_id=None, **extra)— context manager that bindsworker_run_id(8-hex-char auto-generation) and arbitrary extras to all logs emitted in the block. Restores prior bindings on exit, including on exceptions.collide_logging.with_worker_run_id— decorator equivalent.collide_logging.testing.assert_collide_v1(record)— conformance assertion suitable for use withstructlog.testing.capture_logsin downstream test suites.
Framework adapters
collide_logging.django.RequestLoggingMiddleware([django]extra)collide_logging.starlette.RequestLoggingMiddleware([fastapi]extra) — works with Starlette and FastAPIcollide_logging.flask.init_app(app)([flask]extra)
Spec coverage
- Required fields (
timestamp,level,service,logger,event) emitted on every log line - ISO-8601 timestamps with timezone
- Secret redaction by exact field name (case-insensitive) plus the spec-mandated suffix rules
*_token,*_api_token,*_signing_secret - Correlation ID flow via structlog contextvars (
request_idfrom middleware,worker_run_idfrom helpers)