Skip to main content

src/hecate_om_health.erl

%%% @doc Tracks a single service's health snapshot.
%%%
%%% Exposed over HTTP by `hecate_om_health_handler' (Cowboy) at
%%% `GET /health' on port `health_port' (default 8470). Podman's
%%% HEALTHCHECK and systemd's `EXEC_START' Readiness mechanics use it.
-module(hecate_om_health).
-behaviour(gen_server).

-export([start_link/0, register/1, snapshot/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).

-record(state, {
    service_module :: module() | undefined,
    last_health    :: hecate_om_service:health()
}).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

register(ServiceMod) when is_atom(ServiceMod) ->
    gen_server:call(?MODULE, {register, ServiceMod}).

snapshot() ->
    gen_server:call(?MODULE, snapshot).

init([]) ->
    {ok, #state{last_health = {down, not_started}}}.

handle_call({register, Mod}, _From, S) ->
    Health = safely(fun() -> Mod:health() end),
    {reply, ok, S#state{service_module = Mod, last_health = Health}};
handle_call(snapshot, _From, #state{service_module = undefined} = S) ->
    {reply, S#state.last_health, S};
handle_call(snapshot, _From, #state{service_module = Mod} = S) ->
    Health = safely(fun() -> Mod:health() end),
    {reply, Health, S#state{last_health = Health}};
handle_call(_Msg, _From, S) ->
    {reply, {error, unknown_call}, S}.

handle_cast(_Msg, S) -> {noreply, S}.
handle_info(_Msg, S) -> {noreply, S}.
terminate(_Reason, _State) -> ok.

safely(Fun) ->
    try Fun()
    catch C:R -> {down, {C, R}}
    end.