account for alert sending failing

This commit is contained in:
Alex Tau 2025-12-19 16:34:10 +03:00
parent 40e30529eb
commit 10e79d6827
6 changed files with 84 additions and 32 deletions

View file

@ -11,7 +11,9 @@ dependencies = [
"alt-utils>=0.0.8", "alt-utils>=0.0.8",
"humanize>=4.12.3", "humanize>=4.12.3",
"psutil>=7.0.0", "psutil>=7.0.0",
"returns>=0.26.0",
"telethon>=1.40.0", "telethon>=1.40.0",
"tenacity>=9.1.2",
"uplink[aiohttp]>=0.10.0", "uplink[aiohttp]>=0.10.0",
] ]

View file

@ -142,8 +142,9 @@ async def async_main():
if stopping: if stopping:
if "self" in config.enabled_check_sets: if "self" in config.enabled_check_sets:
alert = checks.generate_stop_alert() alert = checks.generate_stop_alert()
await sender.send_alert(alert) async with asyncio.TaskGroup() as tg:
await sender.send_healthchecks_status(alert) tg.create_task(sender.send_alert(alert))
tg.create_task(sender.send_healthchecks_status(alert))
for c in checkers: for c in checkers:
try: try:
await c.graceful_stop() await c.graceful_stop()

View file

@ -1,12 +1,13 @@
import logging import logging
from socket import gethostname
import tenacity
from returns.result import Failure, Success
from telethon import TelegramClient from telethon import TelegramClient
from telethon.sessions import MemorySession from telethon.sessions import MemorySession
from uplink import AiohttpClient from uplink import AiohttpClient
from ..checks.utils import format_for_healthchecks_slug
from ..core import cvars from ..core import cvars
from ..core.error_handling import log_errors_async
from .alert import Alert from .alert import Alert
from .clients.healthchecks import HealthchecksClient from .clients.healthchecks import HealthchecksClient
from .enum import SEVERITY_TO_EMOJI, Severity from .enum import SEVERITY_TO_EMOJI, Severity
@ -38,27 +39,26 @@ def format_message(alert: Alert, note: str) -> str:
return message return message
async def send_alert(alert: Alert, note: str = "") -> None: async def send_alert(alert: Alert, note: str = "") -> Success[None] | Failure[tenacity.RetryError]:
await log_errors_async(_send_alert(alert, note))
async def send_healthchecks_status(alert: Alert) -> Success[None] | Failure[tenacity.RetryError]:
await log_errors_async(_send_healthchecks_status(alert))
@tenacity.retry(wait=tenacity.wait_random_exponential(multiplier=1, max=60))
async def _send_alert(alert: Alert, note: str = "") -> None:
logging.debug(f"Sending {alert.alert_type} alert to Telegram") logging.debug(f"Sending {alert.alert_type} alert to Telegram")
try: tg_client = cvars.tg_client.get()
tg_client = cvars.tg_client.get()
except LookupError: # being called standalone
# cvars.config.set(get_config())
# temp_client = True
# client = await get_tg_client()
# cvars.matrix_client.set(client)
raise NotImplementedError # TODO
else:
... # temp_client = False
if tg_client is not None: if tg_client is not None:
room_id = cvars.config.get().alert_channels.telegram.room_id room_id = cvars.config.get().alert_channels.telegram.room_id
message = format_message(alert, note) message = format_message(alert, note)
await tg_client.send_message(entity=room_id, message=message) await tg_client.send_message(entity=room_id, message=message)
# if temp_client:
# await client.close()
async def send_healthchecks_status(alert: Alert) -> None: @tenacity.retry(wait=tenacity.wait_random_exponential(multiplier=1, max=60))
async def _send_healthchecks_status(alert: Alert) -> None:
def get_pinging_key(keys: dict[str, str]): def get_pinging_key(keys: dict[str, str]):
if alert.healthchecks_slug in keys: if alert.healthchecks_slug in keys:
return keys[alert.healthchecks_slug] return keys[alert.healthchecks_slug]

View file

@ -70,21 +70,22 @@ class BaseChecker:
return result return result
async def _handle_alerts(self, alerts: list[Alert]) -> None: async def _handle_alerts(self, alerts: list[Alert]) -> None:
if not self.is_reminder: async with asyncio.TaskGroup() as tg:
for alert in alerts: if not self.is_reminder:
await send_healthchecks_status(alert) for alert in alerts:
tg.create_task(send_healthchecks_status(alert))
if not self.persistent: if not self.persistent:
for alert in alerts: for alert in alerts:
if alert.severity != Severity.OK: if alert.severity != Severity.OK:
await send_alert(alert, "ongoing" if self.is_reminder else "") tg.create_task(send_alert(alert, "ongoing" if self.is_reminder else ""))
return return
old_severity, new_severity = self.current_alerts.update(alerts) old_severity, new_severity = self.current_alerts.update(alerts)
if (old_severity != new_severity or self.send_any_state) and not ( if (old_severity != new_severity or self.send_any_state) and not (
old_severity == None and new_severity == Severity.OK old_severity == None and new_severity == Severity.OK
): ):
for alert in alerts: for alert in alerts:
await send_alert(alert, note="ongoing") tg.create_task(send_alert(alert, note="ongoing"))
async def run_checker(self) -> None: async def run_checker(self) -> None:
raise NotImplementedError raise NotImplementedError

View file

@ -0,0 +1,23 @@
import logging
import traceback
from typing import Awaitable, Callable, TypeVar
from returns.result import Failure, Success
T = TypeVar("T")
def log_errors(function: Callable[..., T], *args, **kwargs) -> Success[T] | Failure[Exception]:
try:
return Success(function(args, kwargs))
except Exception as e:
logging.error(traceback.format_exc())
return Failure(e)
async def log_errors_async(awaitable: Awaitable[T]) -> Success[T] | Failure[Exception]:
try:
return Success(await awaitable)
except Exception as e:
logging.error(traceback.format_exc())
return Failure(e)

25
uv.lock generated
View file

@ -287,7 +287,9 @@ dependencies = [
{ name = "alt-utils" }, { name = "alt-utils" },
{ name = "humanize" }, { name = "humanize" },
{ name = "psutil" }, { name = "psutil" },
{ name = "returns" },
{ name = "telethon" }, { name = "telethon" },
{ name = "tenacity" },
{ name = "uplink", extra = ["aiohttp"] }, { name = "uplink", extra = ["aiohttp"] },
] ]
@ -299,7 +301,9 @@ requires-dist = [
{ name = "alt-utils", specifier = ">=0.0.8" }, { name = "alt-utils", specifier = ">=0.0.8" },
{ name = "humanize", specifier = ">=4.12.3" }, { name = "humanize", specifier = ">=4.12.3" },
{ name = "psutil", specifier = ">=7.0.0" }, { name = "psutil", specifier = ">=7.0.0" },
{ name = "returns", specifier = ">=0.26.0" },
{ name = "telethon", specifier = ">=1.40.0" }, { name = "telethon", specifier = ">=1.40.0" },
{ name = "tenacity", specifier = ">=9.1.2" },
{ name = "uplink", extras = ["aiohttp"], specifier = ">=0.10.0" }, { name = "uplink", extras = ["aiohttp"], specifier = ">=0.10.0" },
] ]
@ -514,6 +518,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
] ]
[[package]]
name = "returns"
version = "0.26.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/c2/6dda7ef39464568152e35c766a8b49ab1cdb1b03a5891441a7c2fa40dc61/returns-0.26.0.tar.gz", hash = "sha256:180320e0f6e9ea9845330ccfc020f542330f05b7250941d9b9b7c00203fcc3da", size = 105300, upload-time = "2025-07-24T13:11:21.772Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl", hash = "sha256:7cae94c730d6c56ffd9d0f583f7a2c0b32cfe17d141837150c8e6cff3eb30d71", size = 160515, upload-time = "2025-07-24T13:11:20.041Z" },
]
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "4.9.1" version = "4.9.1"
@ -549,6 +565,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/b0/78f74085b6c88c2bf2bec39c67267cd9ba6af24ceaea9654fb0c272a53da/telethon-1.40.0-py3-none-any.whl", hash = "sha256:1aebaca04fd8410968816645bdbcc0baeff55429b6d6bec37e647417bb8e8a2c", size = 744897, upload-time = "2025-09-01T15:32:34.212Z" }, { url = "https://files.pythonhosted.org/packages/ce/b0/78f74085b6c88c2bf2bec39c67267cd9ba6af24ceaea9654fb0c272a53da/telethon-1.40.0-py3-none-any.whl", hash = "sha256:1aebaca04fd8410968816645bdbcc0baeff55429b6d6bec37e647417bb8e8a2c", size = 744897, upload-time = "2025-09-01T15:32:34.212Z" },
] ]
[[package]]
name = "tenacity"
version = "9.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.14.1" version = "4.14.1"