Packages

Behaviour and use macro for Raven monitor integrations

Current section

Files

Jump to
raven_observer_sdk lib code_name_raven probe.ex
Raw

lib/code_name_raven/probe.ex

defmodule CodeNameRaven.Probe do
@moduledoc """
Behaviour for probe monitors — monitors that observe other monitors via
Raven's event bus rather than collecting from an external target directly.
A probe subscribes to events on startup (handled by Raven itself, not the
probe) and reacts to them as they arrive. Unlike a standard integration, a
probe never polls anything — its "collection" is just reading its own
accumulated state, updated as events arrive.
This module is deliberately implementation-free: it defines only the
callback contract. The actual event subscription and dispatch live in
Raven itself (mirroring how `CodeNameRaven.Display`'s `on_metric/3`/
`on_status/3` work — the display module never subscribes to anything
itself either). A probe module built against this behaviour has zero
dependency on any Raven-internal module and compiles standalone.
## Using this behaviour
Replace `use CodeNameRaven.Monitor` with `use CodeNameRaven.Probe`. Then
implement the callbacks your probe needs:
* `probe_init/1` — set up initial state from params (replaces `init/1`)
* `on_metric/3` — react to an arriving metric event
* `on_status/3` — react to an arriving status event (optional)
* `healthy?/1` — evaluate current state → health signal (required,
inherited from `CodeNameRaven.Monitor` — not redefined here)
All callbacks except `healthy?/1` have no-op defaults. A minimal probe
only needs `probe_init/1`, `on_metric/3`, and `healthy?/1`.
## Lifecycle
1. Raven starts the probe's process, sees it's probe-shaped, and
subscribes it to metric and status events on its behalf.
2. Raven calls `init/1` → delegates to `probe_init/1` for state setup.
3. As metric/status events arrive, Raven calls `on_metric/3` or
`on_status/3` directly with the event and the probe's current state.
4. `on_metric/3` / `on_status/3` return `{:ok, new_state}` to update state
and wait, or `{:ok, new_state, :check_now}` to also trigger an
immediate check cycle.
5. On each check cycle, `collect/2` returns `{:ok, state, state}` — the
current state is the collect result.
6. `healthy?/1` receives the state and returns `:up`, `:degraded`,
`:down`, or `:unknown`.
## Event shapes
`on_metric/3`'s first argument and `on_status/3`'s first argument are
loosely typed (`term()`) rather than a concrete struct — the concrete
shape is documented here rather than enforced at compile time, matching
`CodeNameRaven.Display`'s `on_metric/3`/`on_status/3` precedent, since
the real message module lives in Raven itself, not this SDK.
A metric event has (at least) these fields: `monitor_id` (the id of the
monitor that produced it), `metric_name` (string), `value` (number),
`checked_at` (`DateTime.t()`), `target_id` (string or `nil`).
A status event has (at least): `monitor_id`, `status` (`:up | :degraded |
:down | :unknown`), `checked_at`, `target_id`.
Both are plain structs with a stable, additive-only field contract:
Raven may add fields to these events across releases, but will not
remove or repurpose a field a probe already relies on.
## Example
defmodule Integrations.MyProbe do
use CodeNameRaven.Probe
use CodeNameRaven.Display, category: :custom, size_hint: :small
@impl true
def probe_init(%{"monitor_id" => mid} = _params) do
{:ok, %{monitor_id: mid, last_value: nil}}
end
@impl true
def on_metric(%{monitor_id: mid, metric_name: "my_metric", value: v},
_params, %{monitor_id: mid} = state) do
{:ok, %{state | last_value: v}, :check_now}
end
def on_metric(_, _, state), do: {:ok, state}
@impl true
def healthy?(%{last_value: nil}), do: :unknown
def healthy?(%{last_value: v}) when v > 100, do: :down
def healthy?(_), do: :up
end
## Relationship to Monitor
`use CodeNameRaven.Probe` sets up the full `CodeNameRaven.Monitor`
behaviour and overrides two callbacks with probe-specific defaults:
* `init/1` → delegates to `probe_init/1`
* `collect/2` → returns `{:ok, state, state}` (state is the result)
All other Monitor callbacks (`after_check/3`, `metrics/1`, etc.) are
available with their standard defaults and can be overridden normally.
"""
# ---------------------------------------------------------------------------
# Callbacks
# ---------------------------------------------------------------------------
@doc """
Initialises the probe's state from params.
Called by the Probe-managed `init/1`. Return `{:ok, initial_state}` or
`{:error, reason}`.
The default returns `{:ok, %{}}`.
"""
@callback probe_init(params :: map()) :: {:ok, term()} | {:error, String.t()}
@doc """
Handles an arriving metric event.
Called directly by Raven when a metric event matching this probe's
subscription arrives — not routed through `handle_info/3`. Return
`{:ok, new_state}` to update state, or `{:ok, new_state, :check_now}` to
also trigger an immediate check cycle.
The default ignores all metrics and returns the state unchanged.
"""
@callback on_metric(metric :: term(), params :: map(), state :: term()) ::
{:ok, term()} | {:ok, term(), :check_now}
@doc """
Handles an arriving status event.
Called directly by Raven, same delivery mechanism as `on_metric/3`.
Return `{:ok, new_state}` to update state, or `{:ok, new_state,
:check_now}` to also trigger an immediate check cycle.
The default ignores all status events and returns the state unchanged.
"""
@callback on_status(status :: term(), params :: map(), state :: term()) ::
{:ok, term()} | {:ok, term(), :check_now}
@doc """
Returns true if the given module implements the Probe behaviour.
Checks for `probe_init/1`, the one callback every real probe defines
(directly or via inheriting `use CodeNameRaven.Probe`'s default).
"""
@spec probe_module?(module()) :: boolean()
def probe_module?(module) do
Code.ensure_loaded?(module) and function_exported?(module, :probe_init, 1)
end
# ---------------------------------------------------------------------------
# __using__ — sets up the behaviour and provides default implementations
# ---------------------------------------------------------------------------
@doc false
defmacro __using__(_opts \\ []) do
quote do
use CodeNameRaven.Monitor
@behaviour CodeNameRaven.Probe
@impl CodeNameRaven.Monitor
def init(params), do: probe_init(params)
@impl CodeNameRaven.Monitor
def collect(_params, state), do: {:ok, state, state}
@impl CodeNameRaven.Probe
def probe_init(_params), do: {:ok, %{}}
@impl CodeNameRaven.Probe
def on_metric(_metric, _params, state), do: {:ok, state}
@impl CodeNameRaven.Probe
def on_status(_status, _params, state), do: {:ok, state}
defoverridable init: 1,
collect: 2,
probe_init: 1,
on_metric: 3,
on_status: 3
end
end
end