From 68ae808848dc977e9b958e3c1cc01d7a88a9673c Mon Sep 17 00:00:00 2001 From: Antoine Augusti Date: Thu, 19 Dec 2024 09:52:31 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20plug=20healthcheck=20sp=C3=A9cifique=20?= =?UTF-8?q?pour=20le=20worker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/transport_web/plugs/router.ex | 2 +- .../transport_web/plugs/worker_healthcheck.ex | 77 +++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 apps/transport/lib/transport_web/plugs/worker_healthcheck.ex diff --git a/apps/transport/lib/transport_web/plugs/router.ex b/apps/transport/lib/transport_web/plugs/router.ex index d1cbfb54a9..4ee9e686e6 100644 --- a/apps/transport/lib/transport_web/plugs/router.ex +++ b/apps/transport/lib/transport_web/plugs/router.ex @@ -2,7 +2,7 @@ defmodule TransportWeb.Plugs.Router do use Plug.Router plug(TransportWeb.Plugs.HealthCheck, at: "/health-check") - plug(TransportWeb.Plugs.Halt, if: {Transport.Application, :worker_only?}, message: "UP (WORKER-ONLY)") + plug(TransportWeb.Plugs.WorkerHealthcheck, if: {Transport.Application, :worker_only?}) plug(:match) plug(:dispatch) diff --git a/apps/transport/lib/transport_web/plugs/worker_healthcheck.ex b/apps/transport/lib/transport_web/plugs/worker_healthcheck.ex new file mode 100644 index 0000000000..0be5a4d128 --- /dev/null +++ b/apps/transport/lib/transport_web/plugs/worker_healthcheck.ex @@ -0,0 +1,77 @@ +defmodule TransportWeb.Plugs.WorkerHealthcheck do + @moduledoc """ + A plug for the worker. + + It displays: + - when the app was started + - the last attempt for Oban jobs + - if the system is healthy + + The system is considered healthy if the app was started recently or + if Oban attempted jobs recently. + """ + import Plug.Conn + + @app_start_waiting_delay {20, :minute} + @oban_max_delay_since_last_attempt {60, :minute} + + def init(options), do: options + + def call(conn, opts) do + {mod, fun} = opts[:if] + + if apply(mod, fun, []) do + status_code = if healthy_state?(), do: 200, else: 503 + + conn + |> put_resp_content_type("text/plain") + |> send_resp(status_code, """ + UP (WORKER-ONLY) + App start time: #{app_start_datetime()} + App started recently?: #{app_started_recently?()} + Oban last attempt: #{oban_last_attempted_at()} + Oban attempted jobs recently?: #{oban_attempted_jobs_recently?()} + Healthy state?: #{healthy_state?()} + """) + |> halt() + else + conn + end + end + + def healthy_state? do + app_started_recently?() or oban_attempted_jobs_recently?() + end + + def app_started_recently? do + start_datetime = app_start_datetime() + {delay, unit} = @app_start_waiting_delay + DateTime.before?(DateTime.utc_now(), DateTime.add(start_datetime, delay, unit)) + end + + def app_start_datetime do + Transport.Cache.fetch(app_start_datetime_cache_key_name(), fn -> DateTime.utc_now() end, expire: nil) + end + + def app_start_datetime_cache_key_name, do: "#{__MODULE__}::app_start_datetime" + + def oban_attempted_jobs_recently? do + oban_last_attempt = oban_last_attempted_at() + {delay, unit} = @oban_max_delay_since_last_attempt + DateTime.before?(oban_last_attempt, DateTime.add(oban_last_attempt, delay, unit)) + end + + def oban_last_attempted_at do + %Postgrex.Result{rows: [[delay]]} = + DB.Repo.query!(""" + SELECT MAX(attempted_at) + FROM oban_jobs + WHERE state = 'completed' + """) + + case delay do + nil -> DateTime.new!(~D[1970-01-01], ~T[00:00:00.000], "Etc/UTC") + %NaiveDateTime{} = nt -> DateTime.from_naive!(nt, "Etc/UTC") + end + end +end