11import asyncio
2+ import json
3+ import os
24from copy import deepcopy
5+ from datetime import datetime , timedelta
36from typing import Any , Awaitable , Dict , List
47
8+ from loguru import logger
59from prometheus_client import Gauge
610
711from pyth_observer .check import Check , State
1014from pyth_observer .event import DatadogEvent # Used dynamically
1115from pyth_observer .event import LogEvent # Used dynamically
1216from pyth_observer .event import TelegramEvent # Used dynamically
17+ from pyth_observer .event import ZendutyEvent # Used dynamically
1318from pyth_observer .event import Event
19+ from pyth_observer .zenduty import send_zenduty_alert
1420
1521assert DatadogEvent
1622assert LogEvent
1723assert TelegramEvent
24+ assert ZendutyEvent
1825
1926
2027class Dispatch :
@@ -36,6 +43,16 @@ def __init__(self, config, publishers):
3643 "Publisher check failure status" ,
3744 ["check" , "symbol" , "publisher" ],
3845 )
46+ if "ZendutyEvent" in self .config ["events" ]:
47+ self .open_alerts_file = os .environ ["OPEN_ALERTS_FILE" ]
48+ self .open_alerts = self .load_alerts ()
49+
50+ def load_alerts (self ):
51+ try :
52+ with open (self .open_alerts_file , "r" ) as file :
53+ return json .load (file )
54+ except FileNotFoundError :
55+ return {} # Return an empty dict if the file doesn't exist
3956
4057 async def run (self , states : List [State ]):
4158 # First, run each check and store the ones that failed
@@ -62,8 +79,41 @@ async def run(self, states: List[State]):
6279
6380 sent_events .append (event .send ())
6481
82+ if event_type == "ZendutyEvent" :
83+ # Add failed check to open alerts
84+ alert_identifier = (
85+ f"{ check .__class__ .__name__ } -{ check .state ().symbol } "
86+ )
87+ state = check .state ()
88+ if isinstance (state , PublisherState ):
89+ alert_identifier += f"-{ state .publisher_name } "
90+ self .open_alerts [alert_identifier ] = datetime .now ().isoformat ()
91+
6592 await asyncio .gather (* sent_events )
6693
94+ # Check open alerts and resolve those that are older than 2 minutes
95+ if "ZendutyEvent" in self .config ["events" ]:
96+
97+ to_remove = []
98+ current_time = datetime .now ()
99+ for identifier , last_failure in self .open_alerts .items ():
100+ if current_time - datetime .fromisoformat (last_failure ) >= timedelta (
101+ minutes = 2
102+ ):
103+ logger .debug (f"Resolving Zenduty alert { identifier } " )
104+ response = await send_zenduty_alert (
105+ alert_identifier = identifier , message = identifier , resolved = True
106+ )
107+ if response and 200 <= response .status < 300 :
108+ to_remove .append (identifier )
109+
110+ for identifier in to_remove :
111+ del self .open_alerts [identifier ]
112+
113+ # Write open alerts to file to ensure persistence
114+ with open (self .open_alerts_file , "w" ) as file :
115+ json .dump (self .open_alerts , file )
116+
67117 def check_price_feed (self , state : PriceFeedState ) -> List [Check ]:
68118 failed_checks : List [Check ] = []
69119
0 commit comments