Telemetry & tracing
Copy MarkdownChannelClient emits :telemetry events for
connections, messages and channel operations. Attach handlers to build
metrics, dashboards or traces — no configuration is needed inside the
library, and nothing is emitted to payload-privacy-sensitive detail: event
metadata contains only topics, event names, refs and byte sizes.
Event catalog
| Event | Measurements | Metadata |
|---|---|---|
[:channel_client, :connection, :start] | %{system_time} | %{transport, url} |
[:channel_client, :connection, :stop] | %{duration} | %{transport, url, result} + reason on error |
[:channel_client, :connection, :disconnected] | %{} | %{reason} |
[:channel_client, :message, :out] | %{payload_bytes} | %{topic, event, ref} |
[:channel_client, :message, :in] | %{duration, payload_bytes} | %{topic, event, ref, join_ref} |
[:channel_client, :channel_join, :start] | %{system_time} | %{topic} |
[:channel_client, :channel_join, :stop] | %{duration} | %{topic, result} + reason on error |
[:channel_client, :push, :start] | %{system_time} | %{event} |
[:channel_client, :push, :stop] | %{duration} | %{event, result} + reason on error |
[:channel_client, :push, :exception] | %{duration} | %{event, kind, reason, stacktrace} |
Notes:
durationvalues are in native time units — convert withSystem.convert_time_unit/3.- The
start/stop/exceptiontriples follow the standard telemetry span conventions, so APM tools can render them as spans. :pushspans wrap synchronousChannelClient.Channel.push/4calls. A server-side error reply closes the span withresult: :error; a timeout emits an:exceptionevent and re-raises as usual.- Frames dropped by plugs or encoding failures never emit
:message, :out.
Subscribing
# In your application start:
:ok = :telemetry.attach_many(
"my-app-channel-client",
ChannelClient.Telemetry.events(),
&MyApp.Telemetry.handle_event/4,
nil
)defmodule MyApp.Telemetry do
require Logger
def handle_event([:channel_client, :connection, :stop], %{duration: duration}, meta, _) do
Logger.info("channel_client connect #{meta.result} (#{us(duration)}s)")
end
def handle_event([:channel_client, :push, :stop], %{duration: duration}, meta, _) do
Logger.debug("push #{meta.event} -> #{meta.result} in #{us(duration)}s")
end
def handle_event(_name, _measurements, _meta, _config), do: :ok
defp us(native), do: System.convert_time_unit(native, :native, :microsecond)
endBuilding traces
Because every operation follows the span triple shape, tracing backends can correlate events directly:
handle_event([:channel_client, :channel_join, :start], m, meta, config) do
# open a span keyed by {self(), meta.topic}
end
handle_event([:channel_client, :channel_join, :stop], %{duration: d}, meta, config) do
# close the span, record d as its latency, mark red/green by meta.result
end
handle_event([:channel_client, :push, :exception], %{duration: d}, meta, config) do
# close the span as failed with meta.kind / meta.reason
endOpenTelemetry users can convert these pairs into OTel spans in a handler, or use a converter library that speaks the same convention.
Correlating messages across services
To propagate trace context through channel payloads, write an outbound plug that stamps context into the payload and mirror it on your server:
outbound_plugs: [
fn msg, _opts ->
{:cont, %{msg | payload: Map.put(msg.payload || %{}, "_trace_id", get_trace_id())}}
end
]Keep such conventions explicit between client and server; ChannelClient never mutates payloads itself.
Phoenix LiveDashboard
LiveDashboard's metrics page can chart these events out of the box, e.g.
queue a counter for [:channel_client, :message, :in] and a summary for
[:channel_client, :push, :stop] durations.