Add healthcheck

This commit is contained in:
2026-06-02 04:33:12 -04:00
parent 1fd7803f12
commit 242820f9d1
5 changed files with 33 additions and 3 deletions
+4
View File
@@ -9,3 +9,7 @@ MYSQL_ROOT_PASSWORD=root_password
COLLECT_INTERVAL_SECONDS=900 COLLECT_INTERVAL_SECONDS=900
APP_TIMEZONE=Europe/London APP_TIMEZONE=Europe/London
FLASK_SECRET_KEY=replace-with-a-random-string FLASK_SECRET_KEY=replace-with-a-random-string
# Optional: healthcheck.io-style heartbeat URL
# On success: GET <url> | On failure: POST <url>/fail
HEALTHCHECK_URL=https://health.pattinson.org/ping/2009942a-9877-411e-89b4-3d17382c8286
+26 -1
View File
@@ -2,6 +2,8 @@ from __future__ import annotations
import logging import logging
import time import time
import urllib.error
import urllib.request
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any from typing import Any
@@ -73,6 +75,27 @@ def save_reading(device: Device, status: dict[str, Any]) -> None:
) )
def _ping_healthcheck(success: bool, detail: str | None = None) -> None:
url = config.healthcheck_url
if not url:
logger.debug("Healthcheck ping skipped: HEALTHCHECK_URL not configured")
return
try:
if success:
logger.debug("Pinging healthcheck (success): %s", url)
urllib.request.urlopen(url, timeout=10) # noqa: S310
logger.info("Healthcheck ping sent successfully")
else:
fail_url = f"{url}/fail"
logger.debug("Pinging healthcheck (fail): %s%s", fail_url, detail)
payload = (detail or "").encode()
req = urllib.request.Request(fail_url, data=payload, method="POST")
urllib.request.urlopen(req, timeout=10) # noqa: S310
logger.info("Healthcheck fail ping sent: %s", detail)
except Exception as exc: # noqa: BLE001
logger.warning("Healthcheck ping failed: %s", exc)
def collect_once(client: SwitchBotClient) -> None: def collect_once(client: SwitchBotClient) -> None:
logger.info("Starting collection cycle") logger.info("Starting collection cycle")
devices = sync_devices(client) devices = sync_devices(client)
@@ -106,8 +129,10 @@ def main() -> int:
started = time.monotonic() started = time.monotonic()
try: try:
collect_once(client) collect_once(client)
except Exception: _ping_healthcheck(True)
except Exception as exc:
logger.exception("Collector cycle failed") logger.exception("Collector cycle failed")
_ping_healthcheck(False, str(exc))
elapsed = time.monotonic() - started elapsed = time.monotonic() - started
time.sleep(max(1, interval - elapsed)) time.sleep(max(1, interval - elapsed))
+1
View File
@@ -20,6 +20,7 @@ class Config:
collect_interval_seconds: int = int(os.getenv("COLLECT_INTERVAL_SECONDS", "900")) collect_interval_seconds: int = int(os.getenv("COLLECT_INTERVAL_SECONDS", "900"))
app_timezone: str = os.getenv("APP_TIMEZONE", "Europe/London") app_timezone: str = os.getenv("APP_TIMEZONE", "Europe/London")
flask_secret_key: str = os.getenv("FLASK_SECRET_KEY", "dev-only-secret") flask_secret_key: str = os.getenv("FLASK_SECRET_KEY", "dev-only-secret")
healthcheck_url: str | None = os.getenv("HEALTHCHECK_URL")
config = Config() config = Config()
+1 -2
View File
@@ -6,8 +6,7 @@
<section class="hero"> <section class="hero">
<div> <div>
<p class="eyebrow">Today so far</p> <p class="eyebrow">Today so far</p>
<h1>Temperature dashboard</h1> <h1>Vet Temperatures</h1>
<p class="muted">Local timezone: {{ timezone }}. Collector interval: {{ collect_interval_seconds // 60 }} min.</p>
</div> </div>
<a class="button" href="/reports">Make report</a> <a class="button" href="/reports">Make report</a>
</section> </section>
+1
View File
@@ -40,6 +40,7 @@ services:
SWITCHBOT_SECRET: ${SWITCHBOT_SECRET:-} SWITCHBOT_SECRET: ${SWITCHBOT_SECRET:-}
COLLECT_INTERVAL_SECONDS: ${COLLECT_INTERVAL_SECONDS:-900} COLLECT_INTERVAL_SECONDS: ${COLLECT_INTERVAL_SECONDS:-900}
APP_TIMEZONE: ${APP_TIMEZONE:-Europe/London} APP_TIMEZONE: ${APP_TIMEZONE:-Europe/London}
HEALTHCHECK_URL: ${HEALTHCHECK_URL:-}
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy