- Add prometheus-client to base requirements; sentry-sdk to prod - api/metrics.py: define HTTP latency histogram, request/error counters, in-flight gauge - api/middleware.py: extend SecurityAuditMiddleware to observe all four Prometheus collectors per request; low-cardinality path_template label via URL resolver - api/views.py: /metrics/ endpoint (gated by METRICS_ENABLED setting) - api/urls.py: wire /metrics/ route - config/settings/prod.py: METRICS_ENABLED flag; optional Sentry SDK init via SENTRY_DSN env var - ops/prometheus/alerts.yml: Prometheus alert rules for p95 latency SLO (≤500 ms), error rate SLO (<1%), availability, and saturation - ops/prometheus/prometheus.yml: scrape config for app + blackbox healthcheck probe - ops/scripts/backup.sh: pg_dump → S3 STANDARD_IA with retention metadata - ops/scripts/restore.sh: pg_restore from S3 or local file with interactive confirmation guard - ops/scripts/synthetic-check.sh: post-deploy smoke test (healthz, metrics gate, schema, 404 shape) - docs/TRA-249-observability-slos.md: SLO table, PromQL reference queries, alert routing - docs/TRA-249-backup-restore.md: RPO/RTO targets, drill procedure, restore validation steps - docs/TRA-249-release-checklist.md: pre/post-deploy checklist - docs/TRA-249-rollback-runbook.md: decision matrix, app rollback, migration revert, DB restore path Co-Authored-By: Paperclip <noreply@paperclip.ing>
60 lines
1.9 KiB
Python
60 lines
1.9 KiB
Python
import time
|
|
from django.conf import settings
|
|
from django.db import connection, OperationalError
|
|
from django.core.cache import cache
|
|
from django.http import HttpResponse
|
|
from rest_framework.decorators import api_view, permission_classes, authentication_classes
|
|
from rest_framework.permissions import AllowAny, IsAdminUser
|
|
from rest_framework.response import Response
|
|
from rest_framework import status
|
|
|
|
|
|
@api_view(["GET"])
|
|
@authentication_classes([])
|
|
@permission_classes([AllowAny])
|
|
def healthcheck(request):
|
|
"""Liveness + readiness probe endpoint. Returns 200 when all dependencies are reachable."""
|
|
checks = {}
|
|
overall_ok = True
|
|
|
|
# Database
|
|
try:
|
|
connection.ensure_connection()
|
|
checks["db"] = "ok"
|
|
except OperationalError as exc:
|
|
checks["db"] = f"error: {exc}"
|
|
overall_ok = False
|
|
|
|
# Cache / Redis
|
|
try:
|
|
key = "_healthcheck"
|
|
cache.set(key, "1", timeout=5)
|
|
assert cache.get(key) == "1"
|
|
checks["cache"] = "ok"
|
|
except Exception as exc:
|
|
checks["cache"] = f"error: {exc}"
|
|
overall_ok = False
|
|
|
|
http_status = status.HTTP_200_OK if overall_ok else status.HTTP_503_SERVICE_UNAVAILABLE
|
|
return Response(
|
|
{"status": "ok" if overall_ok else "degraded", "checks": checks},
|
|
status=http_status,
|
|
)
|
|
|
|
|
|
@api_view(["GET"])
|
|
@authentication_classes([])
|
|
@permission_classes([AllowAny])
|
|
def metrics(request):
|
|
"""Prometheus metrics scrape endpoint.
|
|
|
|
Restricted to internal networks via METRICS_ALLOWED_HOSTS setting (checked
|
|
at the reverse-proxy level in prod). Returns 403 when the feature flag is
|
|
off so the endpoint is a no-op in environments where it is not configured.
|
|
"""
|
|
if not getattr(settings, "METRICS_ENABLED", False):
|
|
return HttpResponse(status=403)
|
|
|
|
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST
|
|
return HttpResponse(generate_latest(), content_type=CONTENT_TYPE_LATEST)
|