import dataclasses import datetime import logging from tadas.platform import config from tadas.platform import context as contexts from tadas.platform import metrics as metrics_utils STATE_UNHEALTHY = 'UNHEALTHY' STATE_HEALTHY = 'HEALTHY' States = [STATE_HEALTHY, STATE_UNHEALTHY] # tadas is daily FEED_FREQUENCY = datetime.timedelta(days=1) monitoring_logger = logging.getLogger('monitoring') logger = logging.getLogger(__name__) @dataclasses.dataclass class CheckResult: state: str message: str class Sensor: """Represents a monitoring point. Subclasses implement `check()`.""" def __init__(self, name): self.name = name def check(self) -> CheckResult: raise NotImplementedError class FreshnessSensor(Sensor): def __init__(self, threshold: datetime.timedelta, name: str): super().__init__(name) self.threshold = threshold def check(self, report_date: datetime.date) -> CheckResult: now = datetime.datetime.now(tz=datetime.timezone.utc) report_timestamp = datetime.datetime.combine(report_date, datetime.time.min, tzinfo=datetime.timezone.utc) delta = report_timestamp + self.threshold - now freshness_value = round(delta.total_seconds() / 86400, 2) metrics_values = { 'freshness_days': freshness_value, 'latest_report_date': report_date.isoformat(), 'threshold_seconds': self.threshold.total_seconds(), 'delta_seconds': delta.total_seconds(), 'now': now.isoformat(), } base_message = ( f'Freshness: {metrics_values["freshness_days"]}, report_date: {report_date}, Now: {now}' f'Delta: {delta}, Threshold: {self.threshold}') metrics = metrics_utils.Metrics(category='freshness_sensor', context=contexts.load_context()) metrics.add_metric('object', self.name) for key, value in metrics_values.items(): metrics.add_metric(key, value) metrics.send() state = STATE_UNHEALTHY if delta > self.threshold else STATE_HEALTHY return CheckResult(state, base_message) def log_sensor_result(sensor: Sensor, result: CheckResult): monitoring_logger.info(f'SENSOR: {sensor.name}, RESULT: {result.state}, MESSAGE: {result.message}') def run_freshness_check( name: str, report_date: datetime.datetime, threshold: datetime.timedelta, ) -> CheckResult: """Run a FreshnessSensor and log a warning if unhealthy.""" sensor = FreshnessSensor(name=name, threshold=threshold) result = sensor.check(report_date) if result.state == STATE_UNHEALTHY: logger.warning(f'Freshness sensor failed: {result.message}') else: logger.info(f'Freshness sensor passed: {result.message}') return result def freshness_threshold() -> datetime.timedelta: """Project-wide freshness threshold driven by config (env var).""" return datetime.timedelta(hours=config.get('FRESHNESS_THRESHOLD_HOURS'))