From fb5e82786fd272939e73bd48f4147eca5acaf8b9 Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 17 Oct 2025 22:44:27 -0700 Subject: [PATCH 1/2] fix: telemetry middleware implements guard steps Adds telemetry events for guard steps in the telemetry middleware --- lib/reactor/middleware/telemetry.ex | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/lib/reactor/middleware/telemetry.ex b/lib/reactor/middleware/telemetry.ex index 801255b..a60d5f3 100644 --- a/lib/reactor/middleware/telemetry.ex +++ b/lib/reactor/middleware/telemetry.ex @@ -13,6 +13,8 @@ defmodule Reactor.Middleware.Telemetry do * `[:reactor, :run, :stop]` * `[:reactor, :step, :run, :start]` * `[:reactor, :step, :run, :stop]` + * `[:reactor, :step, :guard, :start]` + * `[:reactor, :step, :guard, :stop]` * `[:reactor, :step, :process, :start]` * `[:reactor, :step, :process, :stop]` * `[:reactor, :step, :compensate, :start]` @@ -300,6 +302,73 @@ defmodule Reactor.Middleware.Telemetry do ) end + def event({:guard_start, guard, arguments}, step, %{__MODULE__ => %{metadata: metadata}}) do + metadata = + metadata + |> Map.merge(%{ + step: step, + guard: guard, + arguments: arguments, + status: :guard + }) + + start_time = System.monotonic_time() + Process.put({__MODULE__, :guard_start_time, step.name, guard}, start_time) + + :telemetry.execute( + [:reactor, :step, :guard, :start], + %{system_time: System.system_time()}, + metadata + ) + end + + def event({:guard_fail, guard, result}, step, %{__MODULE__ => %{metadata: metadata}}) do + metadata = + metadata + |> Map.merge(%{ + step: step, + guard: guard, + result: result, + status: :error + }) + + start_time = Process.delete({__MODULE__, :guard_start_time, step.name, guard}) + end_time = System.monotonic_time() + duration = end_time - start_time + + :telemetry.execute( + [:reactor, :step, :guard, :stop], + %{ + system_time: System.system_time(), + duration: duration + }, + metadata + ) + end + + def event({:guard_pass, guard}, step, %{__MODULE__ => %{metadata: metadata}}) do + metadata = + metadata + |> Map.merge(%{ + step: step, + guard: guard, + status: :ok + }) + + start_time = Process.delete({__MODULE__, :guard_start_time, step.name, guard}) + end_time = System.monotonic_time() + duration = end_time - start_time + + :telemetry.execute( + [:reactor, :step, :guard, :stop], + %{ + system_time: System.system_time(), + duration: duration + }, + metadata + ) + end + def event({:compensate_start, reason}, step, %{__MODULE__ => %{metadata: metadata}}) do metadata = metadata From cd80e84431d974ef42b7746f9be0cba96aaecb49 Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 17 Oct 2025 23:13:26 -0700 Subject: [PATCH 2/2] feat: add tests for guard telemetry events Introduces tests to verify telemetry events for guard pass and guard halt scenarios --- test/reactor/middleware/telemetry_test.exs | 115 +++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/test/reactor/middleware/telemetry_test.exs b/test/reactor/middleware/telemetry_test.exs index 136d1ae..fa9d939 100644 --- a/test/reactor/middleware/telemetry_test.exs +++ b/test/reactor/middleware/telemetry_test.exs @@ -16,6 +16,8 @@ defmodule Reactor.Middleware.TelemetryTest do [ [:reactor, :run, :start], [:reactor, :run, :stop], + [:reactor, :step, :guard, :start], + [:reactor, :step, :guard, :stop], [:reactor, :step, :process, :start], [:reactor, :step, :process, :stop], [:reactor, :step, :run, :start], @@ -187,4 +189,117 @@ defmodule Reactor.Middleware.TelemetryTest do [:reactor, :run, :stop] ] = Enum.map(events, & &1.event) end + + test "guard pass events", %{table: table} do + defmodule GuardPassReactor do + @moduledoc false + use Reactor + + middlewares do + middleware Reactor.Middleware.Telemetry + end + + step :noop do + guard fn _args, _context -> + :cont + end + + run fn _, _ -> {:ok, :noop} end + end + end + + {:ok, :noop} = Reactor.run(GuardPassReactor) + + events = get_events(table) + + assert [ + [:reactor, :run, :start], + [:reactor, :step, :process, :start], + [:reactor, :step, :guard, :start], + [:reactor, :step, :guard, :stop], + [:reactor, :step, :run, :start], + [:reactor, :step, :run, :stop], + [:reactor, :step, :process, :stop], + [:reactor, :run, :stop] + ] = Enum.map(events, & &1.event) + + guard_stop = Enum.at(events, 3) + assert guard_stop.metadata.status == :ok + end + + test "guard halt events", %{table: table} do + defmodule GuardHaltReactor do + @moduledoc false + use Reactor + + middlewares do + middleware Reactor.Middleware.Telemetry + end + + step :guard do + guard fn _, _ -> + {:halt, {:error, :hodor}} + end + + run fn _, _ -> {:ok, :noop} end + end + end + + {:error, _} = Reactor.run(GuardHaltReactor) + + events = get_events(table) + + assert [ + [:reactor, :run, :start], + [:reactor, :step, :process, :start], + [:reactor, :step, :guard, :start], + [:reactor, :step, :guard, :stop], + [:reactor, :step, :process, :stop], + [:reactor, :run, :stop] + ] = Enum.map(events, & &1.event) + + guard_stop = Enum.at(events, 3) + assert guard_stop.metadata.status == :error + assert guard_stop.metadata.result == {:error, :hodor} + end + + test "guard fail events", %{table: table} do + defmodule GuardFailReactor do + @moduledoc false + use Reactor + + middlewares do + middleware Reactor.Middleware.Telemetry + end + + step :guard do + guard fn _, _ -> + raise "winter is coming" + end + + run fn _, _ -> {:ok, :noop} end + end + end + + {:error, _} = Reactor.run(GuardFailReactor) + + events = get_events(table) + + assert [ + [:reactor, :run, :start], + [:reactor, :step, :process, :start], + [:reactor, :step, :guard, :start], + [:reactor, :step, :guard, :stop], + [:reactor, :step, :process, :stop], + [:reactor, :run, :stop] + ] = Enum.map(events, & &1.event) + + guard_stop = Enum.at(events, 3) + assert guard_stop.metadata.status == :error + + assert match?( + {:error, %RuntimeError{message: "winter is coming"}}, + guard_stop.metadata.result + ) + end end