Skip to content

feat(relay): the session log says what each session is; class apart from transport - #514

Merged
okdaichi merged 2 commits into
mainfrom
feat/session-log
Oct 12, 2026
Merged

okdaichi merged 2 commits into
mainfrom
feat/session-log

Conversation

@okdaichi

@okdaichi okdaichi commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Why

relay: new session said only where a session came from and whether it was checked:

INFO relay: new session remote=100.109.69.30:52783 checked=true

That is not enough to tell what a client is or what it speaks. Found while looking at foalk-inc/foalk#536: a WebTransport client that offers no subprotocol (an older browser, or a client library that passes none) is served like any other, and nothing in the log distinguishes it from one that agreed on moq-lite-05. Nothing logged a session's end either, apart from a warning from the announcements loop.

The session metrics had a related problem: their transport label said peer or internal for some sessions, which are not transports.

Change

The log

relay: new session now carries:

Attribute
id the session id, the same one as in usage records and in session ended by its grant; absent for a peer or an internal client, which have no connect request
class client, internal or peer: whom the relay takes the session for (below)
transport webtransport, websocket or quic
path the path the session connected to (never the query, which carries the credential)
sub the credential's sub, when it has one
moq the MoQ version the relay speaks on the session
alpn, tls, sni from the TLS handshake, when the transport has one of its own
offered upgraded sessions only: the subprotocols the client offered (WT-Available-Protocols, or Sec-WebSocket-Protocol), as sent. Empty for a client that offered none
user_agent, origin upgraded sessions only, when sent

relay: session ended is new: id, class, transport, remote, the reason (the one in the usage record and the sessions_closed metric), the duration and bytes_sent / bytes_received.

Header values come from the client, so they are logged without control characters and cut at 256 bytes.

Nothing about admission changes. A client that offers no subprotocol is still served.

Class and transport, in the metrics too (breaking for dashboards)

The relay already sorts sessions into three classes (sessionClass, peer_trust.go), because each is asked for something different and allowed something different:

Class Who Admitted by May
client an app's publisher or viewer its credential (or nothing, with auth off) what its grant covers; reported as usage
internal a platform service, such as the HLS egress a certificate from the relay CA, without the peering name subscribe to anything, announce nothing; no usage
peer another relay a certificate from the relay CA with the peering name, or this relay dialed it everything; no usage

For an operator the class answers a different question from the transport: peer bytes are traffic between relays, not what customers sent, and the number of peer sessions is the state of the mesh, not load.

qumo_relay_sessions_total, qumo_relay_sessions_closed_total and qumo_relay_session_bytes_total folded the class into transport, which said peer or internal for those sessions. They now have a class label, and transport is only a transport; peers and internal clients are transport="quic".

  • transport="quic" used to select native-QUIC clients alone. Add class="client" to keep that.
  • transport="peer" and transport="internal" become class="peer" and class="internal".

No dashboard or alert in qumo, qumo-deploy or foalk queries these three metrics.

What moq is, and is not

moq is gomoqt's ConnectionState.Version, which is the version the library speaks, a constant. It is not read from the negotiation. What the client asked for is in alpn (native QUIC) and offered (upgrades); gomoqt refuses an upgrade whose offer has nothing the relay supports, so a non-empty offered on a served session means the two agreed. gomoqt does not expose the selected subprotocol on a Session; if it did, this line could carry it directly.

Output

From the tests:

INFO relay: new session id=cf24… class=client transport=webtransport remote=127.0.0.1:50094 checked=true moq=moq-lite-05 path=/acme/app alpn=h3 tls="TLS 1.3" offered="\"moq-lite-05\"" user_agent="quic-go HTTP/3"
INFO relay: session ended id=ce94… class=client transport=webtransport remote=127.0.0.1:64642 reason=closed duration=1.5s bytes_sent=5456 bytes_received=7116
INFO relay: session ended id=ee4f… class=client transport=quic remote=127.0.0.1:64663 reason=closed duration=25ms bytes_sent=2403 bytes_received=4051
INFO relay: session ended class=internal transport=quic remote=127.0.0.1:64678 reason=closed duration=1.5s bytes_sent=3283 bytes_received=4051
INFO relay: session ended class=peer transport=quic remote=127.0.0.1:64675 reason=closed duration=25ms bytes_sent=4678 bytes_received=4069

Checked

  • go test ./internal/relay/ and go test -tags=integration ./internal/integration/ pass; go vet and golangci-lint run ./internal/relay/ are clean.
  • The lines above were read from those runs. Not run against a browser.

Note for operators

The log now holds sub, user_agent and origin per session, next to the remote address it already held.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PskJnUHF5bxqi5ksWgz63G

okdaichi and others added 2 commits October 12, 2026 17:09
"relay: new session" carried the remote address and whether the session
was checked. It now names the session id, the transport, the path, the
credential's sub, the MoQ version, the ALPN, the TLS version and the SNI,
and for an upgraded session the subprotocols the client offered, its
user agent and its origin. "relay: session ended" is new: the reason,
the duration and the byte totals.

A WebTransport client that offers no subprotocol is served as before;
its line now shows an empty offer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PskJnUHF5bxqi5ksWgz63G
The transport label of the session metrics said "peer" or "internal"
for a relay peer's or an internal client's session. Those are not
transports: both run over native QUIC. The metrics and the session log
now name the class (client, internal, peer) and the transport
(webtransport, websocket, quic) separately.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PskJnUHF5bxqi5ksWgz63G
@okdaichi okdaichi changed the title feat(relay): the session log says what each session is feat(relay): the session log says what each session is; class apart from transport Oct 12, 2026
@okdaichi
okdaichi merged commit 678fcc5 into main Oct 12, 2026
14 checks passed
@okdaichi
okdaichi deleted the feat/session-log branch October 12, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant