Ktor — WebSocket Logging¶


Setup¶
Install both the standard WebSocket plugin and the Wiretap WebSocket plugin:
val client = HttpClient {
install(WebSockets)
install(WiretapKtorWebSocketPlugin) // Logs connections
install(WiretapKtorHttpPlugin) // Logs HTTP (deletes 101 upgrade entries)
}
Automatic Session Wrapping¶
WiretapKtorWebSocketPlugin wraps WebSocket sessions automatically — no extra calls needed. All sent and received frames are logged:
client.webSocket("wss://echo.websocket.org") {
// Send — automatically logged
send(Frame.Text("Hello, server!"))
// Receive — automatically logged as frames are consumed
for (frame in incoming) {
when (frame) {
is Frame.Text -> println("Received: ${frame.readText()}")
is Frame.Binary -> println("Received ${frame.readBytes().size} bytes")
else -> {}
}
}
}
WiretapWebSocketSession API¶
| Method | Description |
|---|---|
send(frame) |
Logs the frame and sends via delegate |
close(code, reason) |
Graceful close, logs status as Closed and closes the delegate |
incoming |
Incoming frames channel with automatic logging (all frame types) |
Auto-Close Detection¶
WiretapWebSocketSession installs a Job.invokeOnCompletion handler that automatically updates the socket status when the session ends — whether from timeout, server close, cancellation, or error.
How It Works¶
WiretapKtorWebSocketPluginhooks intoonResponsefor 101 Switching Protocols responses- Creates a
SocketEntryvia the orchestrator with statusOpen - Stores the socket ID on request attributes
- The plugin automatically wraps the session to intercept
send()and auto-logincomingframes - Connection close/failure is detected automatically via job completion
What Gets Logged¶
Connection¶
- URL (converted to
ws:///wss://) - Request headers
- Status transitions (Open → Closing → Closed / Failed)
- Close code and reason
- Protocol version
Messages¶
- Direction (Sent / Received)
- Content type (Text / Binary / Ping / Pong / Close)
- Content (text string, decoded binary or
[Binary: N bytes], or close code/reason) - Byte count
- Timestamp
Configuration¶
install(WiretapKtorWebSocketPlugin) {
enabled = BuildConfig.DEBUG
// How to render Binary frames. Defaults to Auto.
binaryDecoding = BinaryFrameDecoding.Auto
// = BinaryFrameDecoding.Utf8 // always decode as UTF-8 (replacement chars on invalid bytes)
// = BinaryFrameDecoding.Placeholder // never decode, always show "[Binary: N bytes]"
// = BinaryFrameDecoding.Custom { bytes -> bytes.toHexPreview() }
}
binaryDecoding¶
Auto (default) tries strict UTF-8 — if the payload is valid printable text (tab/LF/CR plus the SignalR Core record separator 0x1E are tolerated, matching what the JSON hub protocol can carry as raw bytes) it's shown as text, otherwise it falls back to [Binary: N bytes]. This lets libraries that ship text-over-binary (e.g. SignalRKore) appear readable without misrepresenting genuine binary like protobuf or MessagePack.
Use Custom for non-UTF-8 charsets, hex previews, or pretty-printing. The decoder is held on the HttpClient for its lifetime — prefer a top-level function or object method reference over a lambda that captures Activity / composable scope.