defmodule Mobus.Stepwise.Telemetry do @moduledoc """ Telemetry integration for the stepwise engine. All engine lifecycle phases emit `:telemetry` span events (`:start`, `:stop`, `:exception` suffixes). ## Engine spans * `[:mobus_stepwise, :engine, :init]` * `[:mobus_stepwise, :engine, :handle_event]` * `[:mobus_stepwise, :engine, :restore]` ## Pipeline events * `[:mobus_stepwise, :pipeline, :action, :start | :stop | :exception]` ## Metadata All events include: * `:execution_id` * `:tenant_id` * `:current_state` * `:meta` — the full orchestration metadata map Engine `handle_event` spans also include: * `:event` — the event name * `:status` — outcome (`:ok`, `:wait`, `:error`) ## Example :telemetry.attach( "my-handler", [:mobus_stepwise, :engine, :handle_event, :stop], fn _event, measurements, metadata, _config -> Logger.info("Event processed in \#{measurements.duration}ns") end, nil ) """ @engine_events [ [:mobus_stepwise, :engine, :init], [:mobus_stepwise, :engine, :handle_event], [:mobus_stepwise, :engine, :restore] ] @pipeline_events [ [:mobus_stepwise, :pipeline, :action] ] @doc "Returns all telemetry event prefixes emitted by the engine." @spec events() :: [list(atom())] def events, do: @engine_events ++ @pipeline_events @doc false def span(event_prefix, metadata, fun) do :telemetry.span(event_prefix, metadata, fun) end @doc false def runtime_metadata(runtime) when is_map(runtime) do %{ execution_id: Map.get(runtime, :execution_id), tenant_id: Map.get(runtime, :tenant_id), current_state: Map.get(runtime, :current_state), meta: Map.get(runtime, :meta, %{}) } end end