Files
buzz/crates/buzz-relay
Will Pfleger fba8a7a94f feat(relay): log WebSocket EVENT writes on the access target (#9154)
Logs every WebSocket `EVENT` on the access target
(`buzz_relay::access`). Before this, accepted writes logged only `Event
ingested` on the handler target, and refused or ephemeral writes logged
nothing.

### What's logged
Each `EVENT` frame writes one `event=write` line when its handler
finishes. The line has `conn_id`, `community`, `pubkey` (the
authenticated login, if any), `kind`, `event_id`, and
`outcome`/`reason`, taken from the OK the relay sent:
- `accepted`
- `rejected` (INFO), with `reason` set to the OK's NIP-01 prefix
(`invalid`, `restricted`, `auth-required`, `duplicate`, `rate-limited`,
…). Any other message is logged as `other`.
- `error` (WARN), when the OK starts with `error`
- `aborted`, when the handler ended before deciding an OK

`outcome` is always the relay's decision. If the OK couldn't be queued
because the outbound channel was full or closed, the line adds
`ok_queued=false`; the field is left off otherwise.

`EVENT`s refused before the handler runs are logged too, where their OK
is sent: admission rate limits, with admission's reason, and a full
handler pool, as `rate-limited`.

The OK text after the prefix, the event's content and its tag values are
never logged. Lines join to `auth`/`close` lines (and so to `peer_addr`)
through `conn_id`, the same way `req` lines do.

Rejected writes are capped per connection by the same refusal-budget
mechanism as refused reads, on their own counter: the first 16
rejections are logged and the rest are only counted, in a new
`writes_suppressed` field on the `close` line. Accepted writes are never
capped.

### How
`access::WriteLog` sends the OK and records it in one call
(`write.ok(...)`). That call replaces every
`conn.send(RelayMessage::ok(...))` in `handle_event` and the
observer-frame handler, so the line always shows what the client was
told.

### Tests
- `access::tests`: the reason comes from the OK prefix only; each
outcome logs at the right level and without content; rejected lines are
capped by the budget and accepted ones are not (draining the channel so
every OK is queued); an OK refused by a full or a closed outbound
channel keeps its outcome and adds `ok_queued=false`.
- `handlers::event::tests::fanout_access`: an unauthenticated `EVENT`
going through `handle_event` writes one `rejected`/`auth-required` line,
with no content or tag values.

---------

Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-11-06 00:46:24 +00:00
..