defmodule CodeNameRaven.Monitor.Checkin do @moduledoc """ Optional mixin for a Monitor that reacts to external check-in pings — Raven's `/checkin/:checkin_id` HTTP endpoint — in addition to (not instead of) its own periodic `collect/2`. This is a narrower, different shape than `CodeNameRaven.Probe`: Probe fully replaces `collect/2` with a reactive stub, for a monitor whose only purpose is watching another monitor's events. A check-in monitor still runs a real, meaningful `collect/2` on every scheduled interval (typically a staleness check against its own last-check-in timestamp) — it just also needs an event-driven hook to update that timestamp when a ping arrives. Layer this alongside a normal `use CodeNameRaven.Monitor`, not in place of it. Like `CodeNameRaven.Probe`, this module is implementation-free: it defines only the callback contract. Raven itself subscribes a check-in-shaped monitor to check-in events on its behalf and calls `on_checkin/3` directly — the module never subscribes to anything itself and has no dependency on any Raven-internal module. ## Using this behaviour defmodule Integrations.MyDeadMansSwitch do use CodeNameRaven.Monitor use CodeNameRaven.Monitor.Checkin @impl true def on_checkin(%{checkin_id: incoming_id}, params, state) do if incoming_id == params[:checkin_id] do {:ok, %{state | last_checkin_at: DateTime.utc_now()}, :check_now} else {:ok, state} end end @impl true def collect(params, state) do # staleness check against state.last_checkin_at end @impl true def healthy?(result), do: # :up / :degraded / :down based on elapsed time end ## Event shape `on_checkin/3`'s first argument is loosely typed (`term()`) rather than a concrete struct, matching `CodeNameRaven.Probe`'s `on_metric/3`/ `on_status/3` precedent. It has (at least) a `checkin_id` field (string — the id from the check-in URL) and a `checkin_at` field (`DateTime.t()`). """ @doc """ Handles an arriving check-in event. Called directly by Raven when a check-in event arrives, for any monitor Raven has recognized as check-in-shaped. Return `{:ok, new_state}` to update state, or `{:ok, new_state, :check_now}` to also trigger an immediate check cycle — the common case, since a fresh check-in usually means "re-evaluate health right now" rather than waiting out the rest of the current interval. The default ignores all check-ins and returns the state unchanged. """ @callback on_checkin(checkin :: term(), params :: map(), state :: term()) :: {:ok, term()} | {:ok, term(), :check_now} @doc """ Returns true if the given module implements this mixin. Checks for `on_checkin/3`, the one callback it adds. """ @spec checkin_module?(module()) :: boolean() def checkin_module?(module) do Code.ensure_loaded?(module) and function_exported?(module, :on_checkin, 3) end @doc false defmacro __using__(_opts \\ []) do quote do @behaviour CodeNameRaven.Monitor.Checkin @impl CodeNameRaven.Monitor.Checkin def on_checkin(_checkin, _params, state), do: {:ok, state} defoverridable on_checkin: 3 end end end