defmodule Phoenix.SocketClient.Telemetry do @moduledoc """ Telemetry integration for Phoenix.SocketClient. This module provides telemetry events for monitoring socket connections, channel joins/leaves, message handling, and connection lifecycle events. """ @doc """ Emits a telemetry event with the given name and measurements/metadata. """ @spec emit_event(list(atom() | String.t()), map(), map()) :: :ok def emit_event(event_name, measurements \\ %{}, metadata \\ %{}) do :telemetry.execute(event_name, measurements, metadata) end # Generic Event Functions @doc """ Emits a generic socket event. """ def socket_event(action, pid, url, measurements, metadata) do base_metadata = %{ pid: pid, url: url, timestamp: System.system_time(:millisecond) } emit_event( [:phoenix_socket_client, :socket], measurements, Map.merge(base_metadata, Map.put(metadata, :action, action)) ) end @doc """ Emits a generic channel event. """ def channel_event(action, pid, topic, measurements, metadata) do base_metadata = %{ pid: pid, topic: topic, timestamp: System.system_time(:millisecond) } emit_event( [:phoenix_socket_client, :channel], measurements, Map.merge(base_metadata, Map.put(metadata, :action, action)) ) end @doc """ Emits a generic message event. """ def message_event(action, pid, topic, event, payload, metadata \\ %{}) do base_metadata = %{ pid: pid, topic: topic, event: event, payload: payload, timestamp: System.system_time(:millisecond) } emit_event( [:phoenix_socket_client, :message], %{}, Map.merge(base_metadata, Map.put(metadata, :action, action)) ) end @doc """ Emits a generic state change event. """ def state_change_event(type, id, old_status, new_status, metadata \\ %{}) do base_metadata = %{ id: id, old_status: old_status, new_status: new_status, timestamp: System.system_time(:millisecond) } emit_event( [:phoenix_socket_client, :state_change], %{}, Map.merge(base_metadata, Map.put(metadata, :type, type)) ) end # Specific Event Functions @doc """ Emits socket connection event. """ @spec socket_connected(pid(), String.t(), map()) :: :ok def socket_connected(pid, url, metadata \\ %{}) do socket_event(:connected, pid, url, %{}, metadata) end @doc """ Emits socket disconnection event. """ @spec socket_disconnected(pid(), String.t(), atom(), map()) :: :ok def socket_disconnected(pid, url, reason, metadata \\ %{}) do socket_event(:disconnected, pid, url, %{}, Map.put(metadata, :reason, reason)) end @doc """ Emits socket connection attempt event. """ @spec socket_connecting(pid(), String.t(), map()) :: :ok def socket_connecting(pid, url, metadata \\ %{}) do socket_event(:connecting, pid, url, %{}, metadata) end @doc """ Emits socket connection error event. """ @spec socket_connection_error(pid(), String.t(), any(), map()) :: :ok def socket_connection_error(pid, url, error, metadata \\ %{}) do socket_event(:connection_error, pid, url, %{}, Map.put(metadata, :error, error)) end @doc """ Emits channel join event. """ @spec channel_joined(pid(), String.t(), pid(), map(), map()) :: :ok def channel_joined(pid, topic, channel_pid, response, metadata \\ %{}) do channel_event( :joined, pid, topic, %{}, Map.merge(metadata, %{channel_pid: channel_pid, response: response}) ) end @doc """ Emits channel join error event. """ @spec channel_join_error(pid(), String.t(), any(), map()) :: :ok def channel_join_error(pid, topic, error, metadata \\ %{}) do channel_event(:join_error, pid, topic, %{}, Map.put(metadata, :error, error)) end @doc """ Emits channel leave event. """ @spec channel_left(pid(), String.t(), atom(), map()) :: :ok def channel_left(pid, topic, reason, metadata \\ %{}) do channel_event(:left, pid, topic, %{}, Map.put(metadata, :reason, reason)) end @doc """ Emits message sent event. """ @spec message_sent(pid(), String.t(), String.t(), map(), map()) :: :ok def message_sent(pid, topic, event, payload, metadata \\ %{}) do message_event(:sent, pid, topic, event, payload, metadata) end @doc """ Emits message received event. """ @spec message_received(pid(), String.t(), String.t(), map(), map()) :: :ok def message_received(pid, topic, event, payload, metadata \\ %{}) do message_event(:received, pid, topic, event, payload, metadata) end @doc """ Emits heartbeat event. """ @spec heartbeat_sent(pid(), String.t(), map()) :: :ok def heartbeat_sent(pid, url, metadata \\ %{}) do socket_event(:heartbeat, pid, url, %{}, metadata) end @doc """ Emits reconnection attempt event. """ @spec reconnecting(pid(), String.t(), integer(), map()) :: :ok def reconnecting(pid, url, attempt, metadata \\ %{}) do socket_event(:reconnecting, pid, url, %{}, Map.put(metadata, :attempt, attempt)) end @doc """ Emits socket status change event. """ @spec socket_status_changed(pid(), String.t(), atom(), atom()) :: :ok def socket_status_changed(pid, url, old_status, new_status) do state_change_event(:socket, url, old_status, new_status, %{pid: pid}) end @doc """ Emits channel status change event. """ @spec channel_status_changed(pid(), String.t(), atom(), atom()) :: :ok def channel_status_changed(pid, topic, old_status, new_status) do state_change_event(:channel, topic, old_status, new_status, %{pid: pid}) end @doc """ Emits socket connection duration event. """ @spec socket_connection_duration(pid(), String.t(), integer()) :: :ok def socket_connection_duration(pid, url, duration) do socket_event(:connection_duration, pid, url, %{duration: duration}, %{}) end @doc """ Emits channel join duration event. """ @spec channel_join_duration(pid(), String.t(), integer()) :: :ok def channel_join_duration(pid, topic, duration) do channel_event(:join_duration, pid, topic, %{duration: duration}, %{}) end @doc """ Emits channel leave duration event. """ @spec channel_leave_duration(pid(), String.t(), integer()) :: :ok def channel_leave_duration(pid, topic, duration) do channel_event(:leave_duration, pid, topic, %{duration: duration}, %{}) end @doc """ Attaches a telemetry handler for debugging purposes. ## Example Phoenix.SocketClient.Telemetry.attach_debug_handler() """ @spec attach_debug_handler() :: :ok def attach_debug_handler do :telemetry.attach_many( "phoenix-socket-client-debug", [ [:phoenix_socket_client, :socket], [:phoenix_socket_client, :channel], [:phoenix_socket_client, :message], [:phoenix_socket_client, :state_change] ], &debug_handler/4, %{} ) end @doc """ Detaches the debug handler. """ @spec detach_debug_handler() :: :ok def detach_debug_handler do :telemetry.detach("phoenix-socket-client-debug") end defp debug_handler(event_name, measurements, metadata, _config) do IO.inspect({event_name, measurements, metadata}, label: "[Phoenix.SocketClient] Telemetry") end end