Kravchenko

Web Lab

АудитБлогКонтакты

Kravchenko

Web Lab

Разрабатываем сайты и автоматизацию на современных фреймворках под ключ

Услуги
ЛендингМногостраничныйВизитка
E-commerceБронированиеПортфолио
Навигация
БлогКонтактыАудит
Обратная связь
+7 921 567-11-16
info@kravlab.ru
с 09:00 до 18:00

© 2026 Все права защищены

•

ИП Кравченко Никита Владимирович

•

ОГРНИП: 324784700339743

Политика конфиденциальности

OpenTelemetry и наблюдаемость в Django/FastAPI: как сократить время на инциденты в 3 раза и держать SLA

Разработка и технологии18 декабря 2025 г.
Покажем, как за 1–2 недели собрать наблюдаемость: логи, метрики и трассировки с OpenTelemetry, Prometheus, Grafana и Jaeger. Дадим готовый docker‑compose, рабочий код на FastAPI и быстрый путь для Django, бизнес‑метрики, алерты и приёмы, чтобы снизить стоимость хранения.
OpenTelemetry и наблюдаемость в Django/FastAPI: как сократить время на инциденты в 3 раза и держать SLA

  • О чем статья
  • Зачем бизнесу наблюдаемость, а не просто «логи»
  • Из чего состоит наблюдаемость: логи, метрики, трассировки
  • Архитектура стеков: open-source и облака, где экономить
  • Быстрый старт через Docker Compose (Jaeger, Prometheus, Grafana)
  • Интеграция в Python/FastAPI: готовый код с метриками и трассировками
  • Django за 5 минут: как включить трассинг и метрики
  • Корреляция логов и трассировок: trace_id в логах без боли
  • Дашборды и алерты: примеры SLO/SLA и PromQL
  • Стоимость и приватность: семплирование, ретеншн, запрет персональных данных
  • Чеклист внедрения
  • Итоги

О чем статья

Мы соберем полноценную наблюдаемость (observability) на базе OpenTelemetry: будем видеть, что происходит внутри сервиса, как быстро отвечают эндпоинты, где теряются секунды, какие ошибки бьют по выручке. Дадим готовые настройки: docker-compose для Jaeger (трейсы), Prometheus + Grafana (метрики), а также рабочий пример FastAPI с метриками и трассировками и короткую инструкцию для Django.

Цель — сократить время на поиск проблем (MTTR) в 2–3 раза, держать SLA без «слепых зон» и быстрее находить узкие места, чтобы экономика сервиса сходилась.

Зачем бизнесу наблюдаемость, а не просто «логи»

  • Снижение времени простоя. Чем быстрее находите и чините, тем меньше потери выручки и репутационные риски.
  • Цифры вместо гипотез. Видно p95/p99 задержек, ошибки по маршрутам и внешним интеграциям, откуда уходят деньги.
  • Контроль SLA и выполнение договоренностей с партнерами.
  • Приоритезация разработки на реальном эффекте: оптимизируем то, что реально тормозит.

Из чего состоит наблюдаемость

Метрики

Числа во времени: ошибки в минуту, задержки ответа, число заказов. Метрики хорошо агрегируются, дешево хранятся и подходят для алертов.

Логи

Подробные записи событий. Нужны для расследований и аудита. Должны быть структурированы (JSON), чтобы их можно было искать и аггрегировать.

Трассировки (трейсинг)

«След» запроса через сервис: какие функции/запросы БД дергались и сколько заняли. Позволяют за минуты ответить «почему медленно именно тут». OpenTelemetry — открытый стандарт для трассировок, метрик и логов.

Архитектура стеков: open-source и облака

Минимальный набор для старта:

  • Jaeger — просмотр трассировок (бесплатно, разворачивается за 1 минуту).
  • Prometheus — сбор метрик, Grafana — дашборды и алерты.
  • Логи — на старте можно оставить в JSON и отправлять в существующий сборщик (например, в облачный лог‑хостинг). Позже — Loki/ClickHouse или облачный провайдер.

Альтернатива — платные APM. Плюсы: быстрее запуск и меньше возни. Минусы: стоимость на объёмах. Этот материал показывает, как собрать «своими руками» и контролировать расходы.

Быстрый старт через Docker Compose

Создадим каталог проекта и папки для приложения и конфигов:

mkdir -p observability-demo/app
cd observability-demo

docker-compose.yml:

version: "3.9"
services:
  jaeger:
    image: jaegertracing/all-in-one:1.57
    ports:
      - "16686:16686"   # UI Jaeger
      - "4317:4317"     # OTLP gRPC для трасс

  prometheus:
    image: prom/prometheus:v2.53.0
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:11.1.0
    depends_on:
      - prometheus
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    ports:
      - "3000:3000"

  app:
    build: ./app
    environment:
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
      - OTEL_TRACES_EXPORTER=otlp
      - OTEL_SERVICE_NAME=shop-api
    ports:
      - "8000:8000"
    depends_on:
      - jaeger

prometheus.yml:

global:
  scrape_interval: 10s
scrape_configs:
  - job_name: "app"
    static_configs:
      - targets: ["app:8000"]
        labels:
          service: "shop-api"

Dockerfile и приложение (FastAPI) поместим в ./app:

app/requirements.txt:

fastapi==0.115.2
uvicorn[standard]==0.32.0
prometheus-client==0.21.0
structlog==24.4.0
requests==2.32.3
opentelemetry-api==1.27.0
opentelemetry-sdk==1.27.0
opentelemetry-exporter-otlp==1.27.0
opentelemetry-instrumentation-fastapi==0.48b0
opentelemetry-instrumentation-requests==0.48b0

app/Dockerfile:

FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
CMD ["python", "main.py"]

app/main.py:

import time
import uuid
from typing import Dict

import requests
import structlog
from fastapi import FastAPI, Request
from prometheus_client import Counter, Histogram, make_asgi_app
from starlette.middleware.base import BaseHTTPMiddleware

# OpenTelemetry (трассировки)
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor

# Настройка трассировок: отправляем в OTLP (Jaeger)
resource = Resource.create({"service.name": "shop-api"})
provider = TracerProvider(resource=resource)
span_exporter = OTLPSpanExporter()
processor = BatchSpanProcessor(span_exporter)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)

# Логи: добавляем trace_id/span_id

def _add_trace_context(_, __, event_dict: Dict):
    span = trace.get_current_span()
    ctx = span.get_span_context() if span else None
    if ctx and ctx.is_valid:
        event_dict["trace_id"] = format(ctx.trace_id, "032x")
        event_dict["span_id"] = format(ctx.span_id, "016x")
    return event_dict

structlog.configure(
    processors=[
        _add_trace_context,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.JSONRenderer(),
    ]
)
log = structlog.get_logger()

# Метрики
REQUESTS = Counter(
    "http_requests_total",
    "Количество HTTP-запросов",
    labelnames=("method", "path", "status_class"),
)
LATENCY = Histogram(
    "http_request_duration_seconds",
    "Задержка HTTP-запросов, секунды",
    labelnames=("method", "path"),
    buckets=(0.025, 0.05, 0.1, 0.25, 0.5, 1, 2, 5)
)
ORDERS_CREATED = Counter(
    "orders_created_total", "Создано заказов", labelnames=("source",)
)

app = FastAPI(title="Shop API")

# Экспорт метрик для Prometheus
app.mount("/metrics", make_asgi_app())

# Middleware для метрик
class MetricsMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        start = time.perf_counter()
        response = await call_next(request)
        path = request.url.path
        # Слишком шумные пути можно схлопывать
        if path.startswith("/order/"):
            path = "/order/{id}"
        if path != "/metrics":
            LATENCY.labels(request.method, path).observe(time.perf_counter() - start)
            status_class = f"{response.status_code // 100}xx"
            REQUESTS.labels(request.method, path, status_class).inc()
        return response

app.add_middleware(MetricsMiddleware)

# Автоинструментирование FastAPI и requests
FastAPIInstrumentor.instrument_app(app)
RequestsInstrumentor().instrument()

@app.get("/health")
def health():
    return {"status": "ok"}

@app.post("/order")
def create_order():
    # Бизнес-метрика
    ORDERS_CREATED.labels(source="web").inc()
    # Имитируем обращение к внешнему сервису оплаты
    with tracer.start_as_current_span("payment"):
        r = requests.get("https://httpbin.org/delay/0.2", timeout=3)
        r.raise_for_status()
    order_id = str(uuid.uuid4())
    log.info("order_created", order_id=order_id, amount=1990, currency="RUB")
    return {"order_id": order_id}

@app.get("/order/{order_id}")
def get_order(order_id: str):
    # Имитируем кэш/БД
    time.sleep(0.03)
    log.info("order_read", order_id=order_id)
    return {"order_id": order_id, "status": "paid"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Запускаем:

docker compose up -d --build

Проверяем:

  • Трассировки: http://localhost:16686 (выберите сервис shop-api).
  • Метрики: http://localhost:9090 (Prometheus) и http://localhost:3000 (Grafana, пароль admin/admin).
  • Сгенерировать трафик: curl -X POST http://localhost:8000/order и curl http://localhost:8000/order/123.

Интеграция в Django за 5 минут

Если у вас Django, добавьте инструментирование трассировок и экспортер OTLP. Установите зависимости:

pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation-django

В wsgi.py подключите инструментатор (раньше, чем создается приложение):

# myproject/wsgi.py
import os
from django.core.wsgi import get_wsgi_application
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.instrumentation.django import DjangoInstrumentor

resource = Resource.create({"service.name": "django-app"})
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)

DjangoInstrumentor().instrument()

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
application = get_wsgi_application()

Метрики экспонируйте через prometheus-client или готовые пакеты (например, django-prometheus). Важно: не забывайте фильтровать path в метриках (слишком детальные пути раздувают кардинальность).

Корреляция логов и трассировок: trace_id в логах

В примере выше мы добавили процессор structlog, который вытаскивает trace_id и span_id из текущего контекста. Это позволяет в любой системе логов быстро провалиться из записи лога в конкретный трейc в Jaeger (по trace_id). Если используете стандартный logging, включите opentelemetry-instrumentation-logging или добавьте фильтр, который подмешивает идентификаторы из opentelemetry.trace.get_current_span().

Правило гигиены: не пишите в логи персональные данные и платежные реквизиты. Идентификаторы пользователей и заказов достаточно.

Дашборды и алерты: примеры SLO/SLA и PromQL

Ключевые сервисные SLI:

  • Ошибки: доля 5xx от всех ответов.
  • Задержка: p95/p99 времени ответа.
  • Нагрузка: RPS по критичным эндпоинтам.

Примеры PromQL:

  • Ошибочная доля за 5 минут:
sum(rate(http_requests_total{status_class="5xx"}[5m]))
/ ignoring(status_class) sum(rate(http_requests_total[5m]))
  • p95 задержки:
histogram_quantile(
  0.95,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)
  • RPS эндпоинта создания заказа:
sum(rate(http_requests_total{path="/order"}[1m]))

Алерты (идеи):

  • p95 > 0.8 сек в течение 5 минут.
  • Ошибки 5xx > 1% дольше 3 минут.
  • RPS внешнего платежного вызова упал на 50% против среднего последних 24 часов.

Бизнес-дашборд в Grafana:

  • Заказы за 1/5/60 минут (orders_created_total).
  • Конверсия (счетчик успешных оплат / счетчик попыток).
  • Время платежа p95 (подспаны payment).

Стоимость и приватность: как не разориться

  • Семплирование трассировок. Для высоконагруженного сервиса оставьте 5–10% спанов (или умное семплирование: 100% ошибок, 10% остального). В OpenTelemetry это настраивается в провайдере или через коллектор.
  • Срок хранения. Метрики храните дольше (90 дней), трейсы — 7–14 дней, логи — по потребности и регуляторике.
  • Кардинальность метрик. Не добавляйте в лейблы уникальные значения (id, email). Раскладывайте пути по шаблонам /order/{id}.
  • Персональные данные. Не логируйте PII. Добавьте фильтры, которые вычищают поля из логов и спанов.

Небольшой кейс: как трассировки экономят часы

Симптом: p95 /order вырос с 150 до 900 мс, конверсия упала. По логам всё «чисто». В Jaeger видно: внутри запроса есть спан payment длительностью ~700 мс. В таймлайне — всплески только в рабочие часы банка. Отключаем повторный DNS‑резолв, включаем Keep‑Alive, договариваемся с провайдером, добавляем таймауты и ретраи. Итог: p95 снова 170–200 мс, конверсия вернулась, поиски заняли 20 минут вместо «полдня». Без трассировок это был бы долгий перебор гипотез.

Чеклист внедрения

  • Согласовали цели: какие SLO важны (ошибки, p95, бизнес‑метрики).
  • Развернули Jaeger, Prometheus, Grafana или выбрали облачный APM.
  • Включили OpenTelemetry в коде (FastAPI/Django), добавили экспортер OTLP.
  • Настроили метрики HTTP и бизнес‑метрики (счетчики заказов, оплат, очередей).
  • Сделали дашборды и алерты (ошибки, задержки, RPS, бизнес‑метрики).
  • Добавили trace_id в логи и убедились, что поиск работает.
  • Включили семплирование и ограничили кардинальность метрик.
  • Прописали политику хранения и очистки, маскирование PII.
  • Провели внутренний тренинг: как искать трейсы, как читать графики.

Итоги

Наблюдаемость — это не «ещё один дашборд». Это системный способ экономить время инженеров, быстрее понимать, где теряются деньги, и держать SLA под контролем. Используя OpenTelemetry, Prometheus, Grafana и Jaeger, можно за 1–2 недели собрать базу, которую легко масштабировать. Начните с метрик HTTP и ключевых бизнес‑счетчиков, включите трассинг на критичных путях, добавьте логи с trace_id — и уже через пару спринтов ваша команда будет тратить на инциденты в разы меньше времени.


DjangoнаблюдаемостьOpenTelemetry