opentelemetry-erlang-contrib icon indicating copy to clipboard operation
opentelemetry-erlang-contrib copied to clipboard

Q: how to propagate ecto context from/to custom worker metrics?

Open elvanja opened this issue 10 months ago • 3 comments

So first up, apologies for making this a feature request kind of issue.

I have workers (GenServer) which emit custom telemetry events and use Ecto. Spans from those workers telemetry are not connected to related Ecto spans.

Currently it looks like this (genserver setup and actual measurements omitted for brevity). Basically we emit telemetry events from worker and attach to them to be able to emit open telemetry spans. While this works, it does not connect ecto spans with the worker spans. And ideally, those would be connected / parented correctly.

defmodule YAAPP.Telemetry do
  def telemetry_start(namespace, event, metadata) do
    start_time = System.monotonic_time()

    execute_telemetry_event(namespace, [event | [:start]], metadata)

    {namespace, event, start_time, metadata}
  end

  def telemetry_stop({namespace, event, start_time, metadata}, new_metadata \\ %{}) do
    metadata = Map.merge(metadata, new_metadata)
    
    execute_telemetry_event(namespace, [event | [:stop]], metadata)
  end

  defp execute_telemetry_event(namespace, event, metadata) do
    :telemetry.execute([:yaapp, namespace | event], %{}, metadata)
  end
end

defmodule YAAPP.Worker do
  use GenServer

  def handle_info(:do_work, state) do
    telemetry = YAAPP.Telemetry.telemetry_start(:worker, :do_work, %{state: state})
    result = do_ecto_stuff()
    YAAPP.Telemetry.telemetry_stop(telemetry, %{state: state, result: result})
    {:noreply, state}
  end

  defp telemetry_start(state, event) do
    YAAPP.Telemetry.telemetry_start(:indexer, event, telemetry_metadata(state))
  end
end

defmodule YAAPP.Otel do
  require OpenTelemetry.Tracer

  def setup do
    :telemetry.attach_many(
      {__MODULE__, :worker_events},
      [[:worker, :do_work, :stop]],
      &__MODULE__.handle_service_event/4,
      %{}
    )
  end

  def handle_service_event(name, metrics, metadata, other) do
    [_ | [function | _] = span_name] = Enum.reverse(name)
    span_id = span_name |> Enum.reverse() |> Enum.join(".")

    OpenTelemetry.Tracer.start_span(span_id, %{})
    |> OpenTelemetry.Span.end_span()

    OpenTelemetry.Ctx.clear()
  end
end

I tried fetching current context in YAAPP.Telemetry, e.g. via OpenTelemetry.Ctx.get_current() and OpentelemetryProcessPropagator.fetch_parent_ctx(1, :"$callers") and propagating via telemetry event metadata. But, those are empty, as expected. Tried then to wrap telemetry event in an otel span, like this:

defmodule YAAPP.Telemetry do
  # ....
  defp execute_telemetry_event(namespace, event, metadata, measurements \\ %{}) do
    OpenTelemetry.Tracer.with_span(__MODULE__) do
      metadata = metadata
                 |> Map.put(:ctx, OpenTelemetry.Ctx.get_current())
                 |> Map.put(:parent_ctx, OpentelemetryProcessPropagator.fetch_parent_ctx(1, :"$callers"))

      :telemetry.execute([:yaapp, namespace | event], measurements, metadata)
    end
  end
end

defmodule YAAPP.Otel do
  # ....

  def handle_service_event(name, metrics, metadata, other) do
    # ....

    # context fetched via metadata
    # parent_ctx is :undefined
    OpenTelemetry.Ctx.attach(metadata.ctx)    

    OpenTelemetry.Tracer.start_span(span_id, %{})
    # ....
  end
end

But, that also did not work. E.g. I still don't see correct parenting of those traces (in Grafana/Tempo for my case).

Any suggestions? 🙇🏼

elvanja avatar Dec 05 '24 20:12 elvanja