Skip to content

blackbull.server.access_log

blackbull.server.access_log

Per-request access-log helpers shared by the HTTP/1.1 and HTTP/2 paths.

AccessLogRecord dataclass

Per-request record populated in two phases.

Phase 1 (after parse): client_ip, method, path, http_version. Phase 2 (during send): status, response_bytes. For WebSocket sessions, close_code is captured on disconnect instead. Emitted as one INFO line on 'blackbull.access' after the response completes.

__str__()

Format once and cache for multiple logging sinks.

finalize()

Snapshot the duration at completion so a later (deferred) format() reports the request's real duration rather than duration + the time the record waited in the logging queue. Idempotent; returns self.

from_conn(conn) classmethod

Snapshot parsed Connection fields without creating an ASGI scope.

mark(name)

Capture wall + CPU clocks for name. No-op when phase tracing is disabled, so callers don't need to guard themselves.

phase_summary()

Format the phase deltas as a→b=Wus|Cus a→b=....

close_record(record)

Mark dispatch done and emit; no-op for None.

close_ws_record(record, close_code)

Emit session close code without a dispatch_done phase; no-op for None.

disconnect_events_observed(aggregator)

Whether the disconnect-detecting receive wrapper is observed.

The wrapper emits request_disconnected and marks the request so request_completed can suppress itself on a dropped one. With neither listener present nothing observes either effect and the actor dispatches the raw receive instead. Body-level disconnect detection (conn.body() → ClientDisconnected) does not go through it.

emit_access_log(record)

Emit on the access logger when INFO is enabled.

Snapshot duration before deferred formatting so queue latency is excluded. Keep structured extra fields eager. User handlers or filters require the standard logging path; direct enqueue must not bypass them.

open_record(conn, aggregator, loop_start=None)

Open a request record when a consumer exists; otherwise return None.

Publish in conn.state, shared by compatibility scopes. loop_start seeds HTTP/1.1 keep-alive timing; snapshot wire fields before application rewrites.

request_record_needed(aggregator)

Whether the per-request AccessLogRecord will be consumed.

Three consumers, and no others: the access log (blackbull.access at INFO), phase tracing, and the request_completed event's wire fields. With none of them active the actor skips building the record at all, so every consumer must tolerate its absence — conn.state['access_log'] reads back as None, and request_completed substitutes '-'/0 placeholders.

start_record(conn)

Build and publish a record unconditionally.

For the two paths request_record_needed cannot answer for: a WebSocket session's record spans the connection and carries close_code, and a pushed response needs one for the sender's inline capture.