Skip to content

Releases: collide-ai/collide-logging-py

v0.5.1 — Starlette exception-path request log

Choose a tag to compare

@heysamtexas heysamtexas released this 11 Jun 16:01
bc2c49b

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.RequestLoggingMiddleware skipped the http.request line when the wrapped app raised (#44). The line was emitted after await self.app(...) returned, so an unhandled exception jumped past it into the finally and the genuinely-unhandled 500 produced no request log. The middleware now emits a status-500 http.request line at error level with the traceback (exc_info) before re-raising. No response exists on that path, so X-Request-ID is not set and the status is reported as 500 even if http.response.start already fired; request_id still 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 its duration_ms is already correct.
  • CLAUDE.md gains 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

Choose a tag to compare

@heysamtexas heysamtexas released this 11 Jun 01:53
45d4f82

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: null for email-auth users (#34). The middleware read user.username, which is None on models with USERNAME_FIELD = "email". It now reads user.get_username(), correct for both default-username and email-auth models.
  • duration_ms measured at stream open for streaming responses (#41). The http.request line is now deferred by hooking response.close(), so duration_ms reflects 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.user can't mask the real error).

Added

  • RequestLoggingMiddleware is now async-capable (#40). Declares sync_capable/async_capable, detects an async get_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

Choose a tag to compare

@heysamtexas heysamtexas released this 11 Jun 01:20
f07e8e8

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_VALIDATE values fail safe to lenient (#37). Previously only the literal "lenient" enabled lenient mode; any other set value (a typo like leniant, stray whitespace, or empty string) silently fell through to raise mode, where a schema violation throws EventValidationError into 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 to lenient with a one-time collide_logging.invalid_validate_mode warning 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

Choose a tag to compare

@heysamtexas heysamtexas released this 11 Jun 01:10
96ea325

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=lenient a 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_violation marker added), with the collide_logging.schema_violation meta-event alongside as an alertable signal. raise mode unchanged.

Added

  • #33 — CollideLogger.event() accepts level= and exc_info=. Schema-validated error-path events can emit at warning/error/etc. and carry a traceback. Defaults leave the happy-path record byte-identical.
  • #35 — public digest_value(). Exposes the {"len", "sha256"} digest behind FieldSpec(redact=True) for hand-curated redaction of sensitive free-text on the plain log.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

Choose a tag to compare

@heysamtexas heysamtexas released this 13 May 04:13
4ac7eb0

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 a structlog.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 the exception field.
  • Existing CollideLogger output 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

Choose a tag to compare

@heysamtexas heysamtexas released this 11 May 19:58
bff0939

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; raises ValueError on 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 — BoundLogger subclass returned by get_logger(). Adds event(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 or raise (dev default): unknown event names, missing required fields, and unknown field keys raise EventValidationError. lenient (prod): drops the offending record and emits a collide_logging.schema_violation meta-event instead. Never crashes the host process.
  • Fields flagged redact=True are replaced with {"len": <bytes>, "sha256": "<first 8 hex>"} before emission. Non-str/bytes values are coerced through repr() first. Global suffix-based redaction (*_token, etc.) still applies on top, unchanged from v0.1.0.

v0.1.0

Choose a tag to compare

@heysamtexas heysamtexas released this 30 Apr 17:52
c49e449

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 emit collide/v1-conformant JSON (or a developer-friendly console renderer for TTYs).
  • collide_logging.get_logger(name) — thin wrapper over structlog.get_logger.
  • collide_logging.bind_worker_run_id(run_id=None, **extra) — context manager that binds worker_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 with structlog.testing.capture_logs in downstream test suites.

Framework adapters

  • collide_logging.django.RequestLoggingMiddleware ([django] extra)
  • collide_logging.starlette.RequestLoggingMiddleware ([fastapi] extra) — works with Starlette and FastAPI
  • collide_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_id from middleware, worker_run_id from helpers)