lib/appsignal_phoenix/channel.ex

defmodule Appsignal.Phoenix.Channel do
  @moduledoc """
  Instruments Phoenix channels.

  ## Usage

  To instrument a Phoenix channel, wrap the contents of your handle_in/3
  function in an `Appsignal.Phoenix.Channel.instrument/5` call:

      defmodule AppsignalPhoenixExampleWeb.RoomChannel do
        use Phoenix.Channel

        def join("room:lobby", _message, socket) do
          {:ok, socket}
        end

        def join("room:" <> _private_room_id, _params, _socket) do
          {:error, %{reason: "unauthorized"}}
        end

        def handle_in("new_msg", %{"body" => body} = params, socket) do
          Appsignal.Phoenix.Channel.instrument(__MODULE__, "new_msg", params, socket, fn ->
            broadcast!(socket, "new_msg", %{body: body})
            {:noreply, socket}
          end)
        end
      end

  """

  require Appsignal.Utils
  @tracer Appsignal.Utils.compile_env(:appsignal, :appsignal_tracer, Appsignal.Tracer)
  @span Appsignal.Utils.compile_env(:appsignal, :appsignal_span, Appsignal.Span)

  def instrument(module, name, socket, fun) do
    instrument(module, name, %{}, socket, fun)
  end

  def instrument(module, name, params, socket, fun) do
    Appsignal.instrument(
      "#{Appsignal.Utils.module_name(module)}##{name}",
      fn span ->
        _ = @span.set_namespace(span, "channel")

        try do
          fun.()
        catch
          kind, reason ->
            stack = __STACKTRACE__

            _ =
              span
              |> @span.set_sample_data("params", params)
              |> @span.set_sample_data("environment", Appsignal.Metadata.metadata(socket))
              |> @span.add_error(kind, reason, stack)
              |> @tracer.close_span()

            @tracer.ignore()
            :erlang.raise(kind, reason, stack)
        else
          result ->
            _ =
              span
              |> @span.set_sample_data("params", params)
              |> @span.set_sample_data("environment", Appsignal.Metadata.metadata(socket))

            result
        end
      end
    )
  end

  def channel_action(module, name, socket, function) do
    instrument(module, name, socket, function)
  end
end