Logging config, Sentry, Prometheus, Grafana, health checks, alerts
Статус: принципы актуальны для Django 5.2/6.0. Sentry, OpenTelemetry, Prometheus exporters и JSON formatters — отдельные продукты; их API и совместимость проверяют по зафиксированным версиям.
Мониторинг отвечает на заранее известные вопросы: доступен ли сервис, соблюдается ли latency SLO, растёт ли очередь. Observability шире: по данным системы можно исследовать новый, заранее не предусмотренный сбой.
Классические сигналы дополняют друг друга:
| Сигнал | Хорошо отвечает на вопрос |
|---|---|
| Лог | Какое дискретное событие произошло? |
| Метрика | Как меняется агрегированное число во времени? |
| Trace | Где запрос провёл время по пути через компоненты? |
Error tracker группирует исключения и связывает их с release; APM часто объединяет трассы и метрики. Это полезные продукты, но ошибка не становится отдельным «столпом» вместо trace.
Сбор всех доступных данных создаёт дорогой шум. Начните с критичных операций:
Для каждой определите SLI: долю успешных событий, распределение длительности и допустимое окно. SLO превращает «кажется медленно» в проверяемую цель и помогает алертить по расходу error budget, а не по случайному всплеску CPU.
Django использует стандартный logging. В container приложение обычно пишет один JSON event на строку в stdout/stderr, а платформа доставляет его в централизованное хранилище.
# settings.py
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": "pythonjsonlogger.json.JsonFormatter",
"fmt": "%(asctime)s %(levelname)s %(name)s %(message)s",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "json",
},
},
"root": {
"handlers": ["console"],
"level": "INFO",
},
}Конкретный import path formatter зависит от версии python-json-logger; он должен быть зафиксирован в lock. Managed platform может принимать структурированные events другим formatter/handler.
При одном root handler дочерние loggers обычно используют propagation. Если одновременно назначить им тот же handler и оставить propagate=True, событие появится дважды.
import logging
logger = logging.getLogger(__name__)
def checkout(*, order, actor):
logger.info(
"checkout_started",
extra={
"order_id": str(order.pk),
"actor_id": str(actor.pk),
"order_total_minor": order.total_minor,
},
)
try:
return charge_order(order=order, actor=actor)
except PaymentUnavailable:
logger.exception(
"checkout_payment_unavailable",
extra={"order_id": str(order.pk)},
)
raiseСтабильное имя события удобно группировать, а поля — фильтровать. Не превращайте всё в ERROR: ожидаемый отказ карты может быть бизнес-результатом/метрикой, а недоступность payment provider — технической ошибкой.
Логируйте исключение там, где оно обрабатывается или получает полезный контекст. Если каждый слой вызывает logger.exception() и снова поднимает ту же ошибку, один сбой создаёт пять почти одинаковых stack traces.
Один request проходит proxy, Django, broker и worker. Связать события помогают:
trace_id/span_id OpenTelemetry;Proxy может передать request ID, но приложение должно валидировать формат и длину либо генерировать свой; иначе клиент создаст log injection/high-cardinality мусор. Контекст храните в contextvars, а не в глобальной переменной, чтобы concurrent async requests не смешались.
Передавайте стандартный trace context во внешние HTTP-запросы и сообщения broker через instrumentation. Не делайте correlation ID Prometheus label: число уникальных значений разрушит metric storage.
Запрещённый минимум:
Authorization;request.body login/payment/webhook;Redaction должна работать в Django, third-party SDK, proxy и log collector. send_default_pii=False одного SDK не очищает собственные сообщения приложения.
Определите retention, доступ и удаление по политике данных. Application log не заменяет audit log. Audit trail для изменения прав/денег требует отдельной целостности, ограниченного доступа и стабильной схемы.
SQL logger django.db.backends на DEBUG полезен локально и в краткой целевой диагностике. Постоянное полное SQL-логирование в production создаёт overhead, объём и риск записи параметров. Для трендов используйте database metrics/APM и sampling slow queries.
Для HTTP полезна модель RED:
Для ресурсов — saturation: CPU, memory, file descriptors, database pool, worker concurrency, broker queue age.
Label должен иметь небольшое ограниченное множество значений:
Хорошо: method=GET, route=/api/posts/{id}, status_group=2xx
Плохо: user_id=..., order_id=..., raw_path=/api/posts/928374Используйте нормализованное имя route, а не URL с ID. Не добавляйте exception message как label. High cardinality увеличивает память и может сделать Prometheus непригодным именно во время инцидента.
Latency измеряют histogram и смотрят p50/p95/p99. Среднее не «врёт», но скрывает распределение и хвост. Выбирайте buckets вокруг SLO и учитывайте, что клиентская latency включает proxy/network, а server span — нет.
Endpoint /metrics содержит внутренние имена, версии и объёмы. Ограничьте его private network, service identity или proxy authentication; не открывайте интернету и не защищайте обычной пользовательской session.
Trace состоит из spans: proxy, Django request, SQL, cache, внешний HTTP, broker publish и worker task. Он помогает увидеть, что из 900 мс запроса 700 мс ушло на один upstream, а не на template.
OpenTelemetry задаёт vendor-neutral API/protocol, но требует exporter/collector и инструментирования. Автоинструментация экономит старт, однако пользовательские spans всё равно нужны вокруг бизнес-операций.
Sampling контролирует стоимость. Head sampling принимает решение в начале и может пропустить редкую ошибку; tail sampling в collector способен сохранить медленные/error traces после завершения, но требует больше инфраструктуры. Не ставьте 100% на высокий traffic без расчёта.
Trace attributes подчиняются тем же правилам secrets/PII/cardinality. SQL statement и HTTP body требуют осторожной настройки.
Типовая инициализация Sentry связывает событие с окружением и immutable release:
import os
import sentry_sdk
sentry_sdk.init(
dsn=os.environ["SENTRY_DSN"],
environment=os.environ["DEPLOY_ENV"],
release=os.environ["RELEASE_SHA"],
send_default_pii=False,
traces_sample_rate=float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "0")),
)У актуального SDK Django integration может включаться автоматически; явная настройка нужна при кастомизации. Sample rate — эксплуатационное решение, а не универсальные 0.1.
Перед включением проверьте server-side scrubbing, attachments, breadcrumbs, request data и local variables. release позволяет увидеть regression после deployment, но correlation не доказывает причинность без сравнения stack/changes.
Не отправляйте ожидаемую ошибку валидации как exception. Настройте grouping, ownership и alert rules, иначе tracker станет вторым шумным inbox.
from django.db import connection
from django.http import JsonResponse
from django.views.decorators.cache import never_cache
@never_cache
def liveness(request):
return JsonResponse({"status": "ok"})
@never_cache
def readiness(request):
try:
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
cursor.fetchone()
except Exception:
return JsonResponse({"status": "unavailable"}, status=503)
return JsonResponse({"status": "ready"})Liveness отвечает только «process request loop работает». Если включить database, общий краткий сбой запустит restart storm всех replicas.
Readiness включает только зависимости, без которых replica нельзя пускать в traffic. Cache или recommendation API могут быть необязательными при корректном fallback — тогда их сбой не должен снимать весь web fleet с балансировки.
Проверка:
health и не мутирует бизнес-данные;Startup probe отделяет медленный запуск от liveness. Readiness после SIGTERM должна стать false до завершения grace period.
Page нужен, когда требуется немедленное действие человека. Ticket/notification — когда проблему можно исправить в рабочее время.
Хороший alert содержит:
Примеры симптомов: доля неуспешных checkout, p95 выше SLO, возраст старейшей critical task, несколько недоступных replicas. CPU 90% может быть полезным диагностическим сигналом, но не всегда требует разбудить человека, если SLO соблюдается.
На низком traffic одна ошибка даст огромный процент, поэтому сочетайте ratio с минимальным числом событий. Multi-window burn-rate alert быстрее ловит сильную аварию и не шумит на короткой единичной ошибке.
Периодически проводите game day: намеренно выключите dependency в staging и проверьте, что сигнал дошёл, runbook верен, а alert действительно закрылся после восстановления.
HTTP:
Database/cache:
Background jobs:
Business:
Не ограничивайтесь host CPU: пользовательский путь часто ломается при здоровых машинах.
Проверяйте, что:
Логирование само способно создать инцидент: заполнить диск, заблокировать stdout pipe или исчерпать бюджет SaaS. Ограничивайте объём, применяйте sampling для шумных событий и контролируйте dropped telemetry.
str(exception).Далее: Базы Данных: Продвинутые темы