indie-status-page/app/services/uptime.py
IndieStatusBot 902133edd3 feat: indie status page MVP -- FastAPI + SQLite
- 8 DB models (services, incidents, monitors, subscribers, etc.)
- Full CRUD API for services, incidents, monitors
- Public status page with live data
- Incident detail page with timeline
- API key authentication
- Uptime monitoring scheduler
- 13 tests passing
- TECHNICAL_DESIGN.md with full spec
2026-04-25 05:00:00 +00:00

59 lines
No EOL
2 KiB
Python

"""Uptime monitoring service — performs HTTP health checks."""
import time
from datetime import datetime
import httpx
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.models import Monitor, MonitorResult
async def check_monitor(monitor: Monitor, db: AsyncSession) -> MonitorResult:
"""Perform a single HTTP health check for a monitor and store the result."""
start = time.monotonic()
try:
async with httpx.AsyncClient() as client:
response = await client.request(
method=monitor.method,
url=monitor.url,
timeout=monitor.timeout_seconds,
follow_redirects=True,
)
elapsed_ms = int((time.monotonic() - start) * 1000)
if response.status_code == monitor.expected_status:
# Check response time threshold for "degraded"
status = "up" if elapsed_ms < 5000 else "degraded"
result = MonitorResult(
monitor_id=monitor.id,
status=status,
response_time_ms=elapsed_ms,
status_code=response.status_code,
error_message=None,
checked_at=datetime.utcnow(),
)
else:
result = MonitorResult(
monitor_id=monitor.id,
status="down",
response_time_ms=elapsed_ms,
status_code=response.status_code,
error_message=f"Expected {monitor.expected_status}, got {response.status_code}",
checked_at=datetime.utcnow(),
)
except Exception as exc:
elapsed_ms = int((time.monotonic() - start) * 1000)
result = MonitorResult(
monitor_id=monitor.id,
status="down",
response_time_ms=elapsed_ms,
status_code=None,
error_message=str(exc)[:500],
checked_at=datetime.utcnow(),
)
db.add(result)
await db.flush()
return result