lib/appsignal_phoenix/view.ex

defmodule Appsignal.Phoenix.View do
  @moduledoc """
  AppSignal.Phoenix.View instruments template rendering.

  ## Installation

  To install `Appsignal.Phoenix.View` into your Phoenix application, `use
  Appsignal.Phoenix.View` in your application's view function in the web
  module, after the existing `use Phoenix.View` line:

    defmodule AppsignalPhoenixExampleWeb do
      # ...

      def view do
        quote do
          use Phoenix.View,
            root: "lib/appsignal_phoenix_example_web/templates",
            namespace: AppsignalPhoenixExampleWeb

          use Appsignal.Phoenix.View # <- Add this line

          # Import convenience functions from controllers
          import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]

          # Include shared imports and aliases for views
          unquote(view_helpers())
        end
      end
      # ...
    end

  """
  defmacro __using__(_) do
    quote do
      Appsignal.Logger.debug("AppSignal.Phoenix.View attached to #{__MODULE__}")

      @before_compile Appsignal.Phoenix.View
    end
  end

  defmacro __before_compile__(_env) do
    quote do
      require Appsignal.Utils
      @span Appsignal.Utils.compile_env(:appsignal, :appsignal_span, Appsignal.Span)
      @tracer Appsignal.Utils.compile_env(:appsignal, :appsignal_tracer, Appsignal.Tracer)

      defoverridable render: 2

      def render(template, assigns) when is_binary(template) do
        {root, _pattern, _names} = __templates__()
        path = Path.join(root, template)

        do_render(@tracer.current_span, path, fn ->
          super(template, assigns)
        end)
      end

      def render(template, assigns) do
        super(template, assigns)
      end

      defp do_render(%Appsignal.Span{} = span, path, fun) do
        Appsignal.instrument("Render #{path}", fn span ->
          _ =
            span
            |> @span.set_attribute("title", path)
            |> @span.set_attribute("appsignal:category", "render.phoenix_template")

          fun.()
        end)
      end

      defp do_render(_span, _path, fun) do
        fun.()
      end
    end
  end
end