Automated Action 1501f02451 Add automatic background scheduler for uptime monitoring
- Implemented APScheduler for automatic uptime checks
- Each monitor runs on its own configured interval (default 5 minutes)
- Scheduler starts automatically on application startup
- Real-time job management when monitors are created/updated/deleted
- Added scheduler status and control endpoints
- Background logging of all check results
- Updated documentation with scheduling features
2025-06-20 11:33:47 +00:00

135 lines
4.8 KiB
Python

import logging
from datetime import datetime, timedelta
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
from sqlalchemy.orm import sessionmaker
from app.db.session import engine
from app.models.monitor import Monitor
from app.services.uptime_checker import UptimeChecker
logger = logging.getLogger(__name__)
class UptimeScheduler:
def __init__(self):
self.scheduler = BackgroundScheduler()
self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
self.is_running = False
def start(self):
if not self.is_running:
self.scheduler.start()
self.is_running = True
logger.info("Uptime scheduler started")
self.schedule_all_monitors()
def stop(self):
if self.is_running:
self.scheduler.shutdown()
self.is_running = False
logger.info("Uptime scheduler stopped")
def schedule_all_monitors(self):
"""Schedule all active monitors based on their individual intervals"""
db = self.SessionLocal()
try:
active_monitors = db.query(Monitor).filter(Monitor.is_active).all()
for monitor in active_monitors:
job_id = f"monitor_{monitor.id}"
# Remove existing job if it exists
if self.scheduler.get_job(job_id):
self.scheduler.remove_job(job_id)
# Add new job with the monitor's interval
self.scheduler.add_job(
func=self.check_monitor,
trigger=IntervalTrigger(seconds=monitor.interval),
id=job_id,
args=[monitor.id],
name=f"Check {monitor.name}",
replace_existing=True,
next_run_time=datetime.now()
+ timedelta(seconds=10), # Start after 10 seconds
)
logger.info(
f"Scheduled monitor '{monitor.name}' (ID: {monitor.id}) to run every {monitor.interval} seconds"
)
except Exception as e:
logger.error(f"Error scheduling monitors: {e}")
finally:
db.close()
def check_monitor(self, monitor_id: int):
"""Run uptime check for a specific monitor"""
db = self.SessionLocal()
try:
monitor = db.query(Monitor).filter(Monitor.id == monitor_id).first()
if monitor and monitor.is_active:
checker = UptimeChecker(db)
result = checker.check_monitor(monitor)
status = "UP" if result["is_up"] else "DOWN"
logger.info(
f"Monitor '{monitor.name}' (ID: {monitor_id}): {status} - Response time: {result['response_time']}ms"
)
else:
# Monitor was deleted or deactivated, remove the job
job_id = f"monitor_{monitor_id}"
if self.scheduler.get_job(job_id):
self.scheduler.remove_job(job_id)
logger.info(
f"Removed job for inactive/deleted monitor ID: {monitor_id}"
)
except Exception as e:
logger.error(f"Error checking monitor {monitor_id}: {e}")
finally:
db.close()
def add_monitor_job(self, monitor_id: int, interval: int, name: str):
"""Add a job for a new monitor"""
job_id = f"monitor_{monitor_id}"
# Remove existing job if it exists
if self.scheduler.get_job(job_id):
self.scheduler.remove_job(job_id)
# Add new job
self.scheduler.add_job(
func=self.check_monitor,
trigger=IntervalTrigger(seconds=interval),
id=job_id,
args=[monitor_id],
name=f"Check {name}",
replace_existing=True,
next_run_time=datetime.now() + timedelta(seconds=10),
)
logger.info(
f"Added scheduler job for monitor '{name}' (ID: {monitor_id}) with {interval} second interval"
)
def remove_monitor_job(self, monitor_id: int):
"""Remove a job for a deleted monitor"""
job_id = f"monitor_{monitor_id}"
if self.scheduler.get_job(job_id):
self.scheduler.remove_job(job_id)
logger.info(f"Removed scheduler job for monitor ID: {monitor_id}")
def update_monitor_job(
self, monitor_id: int, interval: int, name: str, is_active: bool
):
"""Update a job for a modified monitor"""
if is_active:
self.add_monitor_job(monitor_id, interval, name)
else:
self.remove_monitor_job(monitor_id)
# Global scheduler instance
uptime_scheduler = UptimeScheduler()