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.IntegrationLogger.debug("AppSignal.Phoenix.View attached to #{__MODULE__}")

      @before_compile Appsignal.Phoenix.View
    end
  end

  defmacro __before_compile__(_env) do
    quote do
      if Module.defines?(__MODULE__, {:__templates__, 0}) &&
           Module.defines?(__MODULE__, {:render, 2}) 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
      else
        Appsignal.Utils.warning("""
        AppSignal.Phoenix.View NOT attached to #{__MODULE__}

        Template rendering instrumentation is now set up automatically, so using
        Appsignal.Phoenix.View in your app's web module is no longer needed.
        """)
      end
    end
  end
end