alert_check_task.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """
  2. Alert Rule Check Task
  3. Celery Beat task to check alert rules and send notifications
  4. """
  5. import logging
  6. import time
  7. from datetime import datetime, timedelta, timezone
  8. import click
  9. import app
  10. from extensions.ext_redis import redis_client
  11. from services.alert_service import AlertService
  12. logger = logging.getLogger(__name__)
  13. @app.celery.task(queue="dataset")
  14. def check_alert_rules_task():
  15. """
  16. Periodically check all enabled alert rules and trigger notifications if thresholds are exceeded.
  17. This task should be run every minute via Celery Beat.
  18. Each rule has its own check_interval_minutes that determines how often it should be checked.
  19. """
  20. logger.info(click.style("Start checking alert rules", fg="green"))
  21. start_at = time.perf_counter()
  22. try:
  23. # Get all enabled rules
  24. rules = AlertService.get_all_enabled_rules()
  25. logger.info(click.style(f"Found {len(rules)} enabled alert rules", fg="green"))
  26. for rule in rules:
  27. try:
  28. # Check if it's time to check this rule
  29. now = datetime.now(timezone.utc)
  30. last_triggered_str = rule.get("last_triggered_at", "")
  31. if last_triggered_str:
  32. try:
  33. last_triggered_at = datetime.fromisoformat(last_triggered_str)
  34. # Calculate next check time
  35. next_check_time = last_triggered_at + timedelta(minutes=int(rule["check_interval_minutes"]))
  36. if now < next_check_time:
  37. logger.debug(f"Skipping rule {rule['id']} - next check at {next_check_time}, now is {now}")
  38. continue
  39. except (ValueError, TypeError):
  40. # Invalid timestamp, proceed with check
  41. pass
  42. # Get current metric value
  43. metric_value = AlertService.get_metric_value(
  44. rule["app_id"], rule["metric_type"], time_range_minutes=int(rule["check_interval_minutes"])
  45. )
  46. logger.debug(
  47. f"Rule {rule['id']}: metric_type={rule['metric_type']}, value={metric_value}, "
  48. f"threshold={rule['operator']} {rule['threshold_value']}"
  49. )
  50. # Check if threshold is exceeded
  51. is_triggered = AlertService.check_threshold(
  52. metric_value, rule["operator"], float(rule["threshold_value"])
  53. )
  54. rule_key = AlertService._get_rule_key(rule["id"])
  55. if is_triggered:
  56. # Increment consecutive trigger count
  57. consecutive_count = int(rule.get("consecutive_trigger_count", 0)) + 1
  58. logger.info(
  59. f"Rule {rule['id']} threshold exceeded: {metric_value} {rule['operator']} {rule['threshold_value']} "
  60. f"(consecutive: {consecutive_count}/{rule['consecutive_triggers']})"
  61. )
  62. # Update consecutive count
  63. redis_client.hset(rule_key, "consecutive_trigger_count", str(consecutive_count))
  64. # Check if we've reached the consecutive trigger threshold
  65. if consecutive_count >= int(rule["consecutive_triggers"]):
  66. logger.info(f"Rule {rule['id']} reached consecutive trigger threshold, sending notifications")
  67. # Trigger alert and send notifications
  68. AlertService.trigger_alert(rule, metric_value)
  69. # Reset consecutive count after triggering (done in trigger_alert via update)
  70. redis_client.hset(rule_key, "consecutive_trigger_count", "0")
  71. else:
  72. # Reset consecutive count if not triggered
  73. consecutive_count = int(rule.get("consecutive_trigger_count", 0))
  74. if consecutive_count > 0:
  75. logger.debug(f"Rule {rule['id']} no longer triggered, resetting consecutive count")
  76. redis_client.hset(rule_key, "consecutive_trigger_count", "0")
  77. # Update last check time
  78. redis_client.hset(rule_key, "last_triggered_at", now.isoformat())
  79. redis_client.expire(rule_key, AlertService.RETENTION_SECONDS)
  80. except Exception:
  81. logger.exception(f"Error checking alert rule {rule.get('id', 'unknown')}")
  82. continue
  83. end_at = time.perf_counter()
  84. logger.info(click.style(f"Alert rule check completed: latency: {end_at - start_at}", fg="green"))
  85. except Exception:
  86. logger.exception("Error in alert check task")