diff --git a/lib/tracing.ex b/lib/tracing.ex index 91cebc1..fdddcd3 100644 --- a/lib/tracing.ex +++ b/lib/tracing.ex @@ -181,11 +181,9 @@ defmodule Tracing do Enum.each(elements, &(:ok = setup_element(&1))) end - def setup_element(:phoenix) do - OpentelemetryPhoenix.setup() - end - - def setup_element(:oban), do: Tracing.ObanTelemetry.setup() def setup_element(:aws), do: Tracing.AWSTelemetry.setup() def setup_element(:chromic_pdf), do: Tracing.ChromicPDFTelemetry.setup() + def setup_element(:liveview), do: Tracing.LiveviewTelemetry.setup() + def setup_element(:oban), do: Tracing.ObanTelemetry.setup() + def setup_element(:phoenix), do: OpentelemetryPhoenix.setup() end diff --git a/lib/tracing/liveview_telemetry.ex b/lib/tracing/liveview_telemetry.ex new file mode 100644 index 0000000..9ae5ba8 --- /dev/null +++ b/lib/tracing/liveview_telemetry.ex @@ -0,0 +1,99 @@ +defmodule Tracing.LiveviewTelemetry do + require OpenTelemetry.Tracer + alias __MODULE__ + alias OpenTelemetry.Span + + @tracer_id LiveviewTelemetry + + @event_names [ + [:phoenix, :live_view, :mount, :start], + [:phoenix, :live_view, :mount, :stop], + [:phoenix, :live_view, :mount, :exception], + [:phoenix, :live_view, :handle_params, :start], + [:phoenix, :live_view, :handle_params, :stop], + [:phoenix, :live_view, :handle_params, :exception], + [:phoenix, :live_view, :handle_event, :start], + [:phoenix, :live_view, :handle_event, :stop], + [:phoenix, :live_view, :handle_event, :exception], + [:phoenix, :live_component, :handle_event, :start], + [:phoenix, :live_component, :handle_event, :stop], + [:phoenix, :live_component, :handle_event, :exception] + ] + def setup do + :telemetry.attach_many( + LiveviewTelemetry, + @event_names, + &LiveviewTelemetry.handle_event/4, + %{} + ) + end + + def handle_event([:phoenix, source, function, :start], _measurements, meta, _config) + when source in [:live_view, :live_component] do + %{socket: %{view: live_view}} = meta + + OpentelemetryTelemetry.start_telemetry_span( + @tracer_id, + "#{inspect(live_view)}.#{function}", + meta, + %{kind: :internal} + ) + |> Span.set_attributes(meta_based_attributes(meta, source, function)) + end + + def handle_event([:phoenix, source, _function, :stop], _measurements, meta, _config) + when source in [:live_view, :live_component] do + OpentelemetryTelemetry.end_telemetry_span(@tracer_id, meta) + end + + def handle_event( + [:phoenix, source, _kind, :exception], + _measurements, + %{kind: kind, reason: reason, stacktrace: stacktrace} = meta, + _config + ) + when source in [:live_view, :live_component] do + ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, meta) + + exception = Exception.normalize(kind, reason, stacktrace) + + Span.record_exception(ctx, exception, stacktrace, []) + Span.set_status(ctx, OpenTelemetry.status(:error, Exception.message(exception))) + + OpentelemetryTelemetry.end_telemetry_span(@tracer_id, meta) + end + + defp meta_based_attributes(meta, source, function) do + module = + case {source, meta} do + {:live_view, _} -> module_to_string(meta.socket.view) + {:live_component, %{component: component}} -> module_to_string(component) + end + + attributes = [ + "liveview.module": module, + "liveview.callback": Atom.to_string(function) + ] + + Enum.reduce(meta, attributes, fn + {:uri, uri}, acc -> + Keyword.put(acc, :"liveview.uri", uri) + + {:component, component}, acc -> + Keyword.put(acc, :"liveview.module", module_to_string(component)) + + {:event, event}, acc -> + Keyword.put(acc, :"liveview.event", event) + + _, acc -> + acc + end) + end + + defp module_to_string(module) when is_atom(module) do + case to_string(module) do + "Elixir." <> name -> name + erlang_module -> ":#{erlang_module}" + end + end +end