ZenWebsocket.Recorder (ZenWebsocket v0.4.2)

Copy Markdown View Source

Pure functions for WebSocket session recording and replay.

Records WebSocket frames to JSONL format (one JSON object per line) for debugging, testing, and analysis. Each entry includes timestamp, direction, frame type, and data.

JSONL Format

{"ts":"2026-01-20T15:30:45.123456Z","dir":"out","type":"text","data":"..."}
{"ts":"2026-01-20T15:30:45.234567Z","dir":"in","type":"text","data":"..."}
{"ts":"2026-01-20T15:30:46.000000Z","dir":"in","type":"binary","data":"base64...","binary":true}

Usage

# Format an entry for recording
line = Recorder.format_entry(:out, {:text, "hello"}, DateTime.utc_now())

# Parse an entry from a recording
{:ok, entry} = Recorder.parse_entry(line)

# Replay a recording
Recorder.replay("/tmp/session.jsonl", fn entry ->
  # Process each entry (e.g., send to handler, accumulate stats)
  handle_entry(entry)
end)

# Get metadata about a recording
{:ok, meta} = Recorder.metadata("/tmp/session.jsonl")
# => %{count: 150, duration_ms: 5234, first_ts: ~U[...], last_ts: ~U[...]}

API Functions

FunctionArityDescriptionParam Kinds
metadata1Get metadata about a recorded session.path: value
replay3Replay a recorded session, calling handler for each entry.path: value, handler_fn: value, opts: value
parse_entry1Parse a JSONL line back into an entry map.line: value
format_entry3Format a WebSocket frame as a JSONL line for recording.direction: value, frame: value, timestamp: value

Summary

Functions

Formats a WebSocket frame as a JSONL line for recording.

Gets metadata about a recorded session.

Parses a JSONL line back into an entry map.

Replays a recorded session by streaming the file and calling the handler for each entry.

Types

direction()

@type direction() :: :in | :out

entry()

@type entry() :: %{
  ts: DateTime.t(),
  dir: direction(),
  type: :text | :binary | :close,
  data: binary(),
  binary: boolean()
}

frame()

@type frame() ::
  {:text, binary()} | {:binary, binary()} | {:close, integer(), binary()}

Functions

format_entry(direction, frame, timestamp \\ DateTime.utc_now())

@spec format_entry(direction(), frame(), DateTime.t()) :: binary()

Formats a WebSocket frame as a JSONL line for recording.

Parameters

  • direction - :in for received frames, :out for sent frames
  • frame - The WebSocket frame tuple {:text, data}, {:binary, data}, or {:close, code, reason}
  • timestamp - The timestamp for this entry (default: current UTC time)

Examples

iex> line = Recorder.format_entry(:out, {:text, "hello"}, ~U[2026-01-20 15:30:45.123456Z])
~s({"ts":"2026-01-20T15:30:45.123456Z","dir":"out","type":"text","data":"hello"})

iex> line = Recorder.format_entry(:in, {:binary, <<1, 2, 3>>}, ~U[2026-01-20 15:30:45Z])
~s({"ts":"2026-01-20T15:30:45.000000Z","dir":"in","type":"binary","data":"AQID","binary":true})

metadata(path)

@spec metadata(binary()) :: {:ok, map()} | {:error, term()}

Gets metadata about a recorded session.

Returns information about the recording including entry count, duration, and timestamp range.

Examples

{:ok, meta} = Recorder.metadata("/tmp/session.jsonl")
# => %{
#      count: 150,
#      duration_ms: 5234,
#      first_ts: ~U[2026-01-20 15:30:45.123456Z],
#      last_ts: ~U[2026-01-20 15:30:50.357456Z],
#      inbound: 100,
#      outbound: 50
#    }

parse_entry(line)

@spec parse_entry(binary()) :: {:ok, entry()} | {:error, term()}

Parses a JSONL line back into an entry map.

Examples

iex> {:ok, entry} = Recorder.parse_entry(~s({"ts":"2026-01-20T15:30:45.123456Z","dir":"out","type":"text","data":"hello"}))
iex> entry.dir
:out
iex> entry.data
"hello"

replay(path, handler_fn, opts \\ [])

@spec replay(binary(), (entry() -> any()), keyword()) :: :ok | {:error, term()}

Replays a recorded session by streaming the file and calling the handler for each entry.

Options

  • :realtime - If true, delays between entries match the original timing (default: false)

Examples

# Fast replay
Recorder.replay("/tmp/session.jsonl", fn entry ->
  IO.puts("#{entry.dir}: #{entry.type}")
end)

# Realtime replay
Recorder.replay("/tmp/session.jsonl", &IO.inspect/1, realtime: true)