Подписывайтесь:

Блог AST-SoftPro

Надёжность системы: мониторинг, алертинг, логирование

05.07.2026 13 мин чтения
Надёжность системы: мониторинг, алертинг, логирование

Надёжность системы: мониторинг, алертинг, логирование

Надёжная система — это не система, которая никогда не ломается. Это система, которая быстро обнаруживает проблемы, уведомляет команду и восстанавливается с минимальным влиянием на пользователей. В этой статье разберём три кита надёжности: мониторинг, алертинг и логирование.

Три столпа наблюдаемости

Наблюдаемость (observability) — способность понять внутреннее состояние системы по внешним данным. Три столпа:

  1. Метрики — числовые показатели (RPS, latency, error rate)

  2. Логи — текстовые записи о событиях

  3. Трейсы — путь запроса через систему

Метрики: что измерять

Без метрик вы не знаете, что происходит в системе. Основные метрики — RED и USE.

RED-метрики (для сервисов):

  • Rate — количество запросов в секунду

  • Errors — количество ошибок

  • Duration — время выполнения запроса

USE-метрики (для ресурсов):

  • Utilization — насколько ресурс загружен

  • Saturation — насколько ресурс близок к пределу

  • Errors — ошибки ресурса

Собираем метрики на Python:

from prometheus_client import Counter, Histogram, Gauge, start_http_server
import time

# Запуск Prometheus exporter
start_http_server(8000)

# Счётчик запросов
REQUEST_COUNT = Counter(
    "http_requests_total",
    "Total HTTP requests",
    ["method", "endpoint", "status"],
)

# Гистограмма времени выполнения
REQUEST_DURATION = Histogram(
    "http_request_duration_seconds",
    "HTTP request duration",
    ["method", "endpoint"],
    buckets=[0.01, 0.05, 0.1, 0.5, 1.0, 5.0],
)

# Счётчик ошибок
ERROR_COUNT = Counter(
    "http_errors_total",
    "Total HTTP errors",
    ["method", "endpoint", "error_type"],
)

# Использование памяти воркеров
WORKER_MEMORY = Gauge(
    "worker_memory_bytes",
    "Worker memory usage",
    ["worker_id"],
)

# Middleware для FastAPI
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware

app = FastAPI()

class MetricsMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        start = time.time()
        try:
            response = await call_next(request)
            duration = time.time() - start
            REQUEST_COUNT.labels(
                method=request.method,
                endpoint=request.url.path,
                status=response.status_code,
            ).inc()
            REQUEST_DURATION.labels(
                method=request.method,
                endpoint=request.url.path,
            ).observe(duration)
            return response
        except Exception as e:
            duration = time.time() - start
            ERROR_COUNT.labels(
                method=request.method,
                endpoint=request.url.path,
                error_type=type(e).__name__,
            ).inc()
            raise

app.add_middleware(MetricsMiddleware)

Grafana: визуализация метрик

Grafana — стандартный инструмент для визуализации метрик Prometheus. Дашборды позволяют видеть состояние системы в реальном времени.

Ключевые дашборды:

  1. Overview — общая картина: RPS, ошибки, latency по всем сервисам

  2. Детали сервиса — метрики конкретного сервиса

  3. Infrastructure — CPU, память, диск, сеть

  4. Business — заказы, пользователи, конверсия

Пример алерта в Grafana:

# Alert: High Error Rate
Condition: http_errors_total / http_requests_total > 0.05
For: 5m
Severity: critical

Логирование: структурированные логи

Структурированные логи (в JSON-формате) — основа отладки. Они позволяют фильтровать, агрегировать и искать по логам.

Структурированное логирование на Python:

import logging
import json
from logging import Handler

class JsonFormatter(logging.Formatter):
    def format(self, record):
        log_entry = {
            "timestamp": self.formatTime(record),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "function": record.funcName,
            "line": record.lineno,
        }
        if hasattr(record, "request_id"):
            log_entry["request_id"] = record.request_id
        if hasattr(record, "user_id"):
            log_entry["user_id"] = record.user_id
        if record.exc_info:
            log_entry["exception"] = self.formatException(record.exc_info)
        return json.dumps(log_entry, ensure_ascii=False)

# Настройка логгера
logger = logging.getLogger("shop")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)

# Использование
logger.info("Order created", extra={"request_id": "abc-123", "user_id": 42})
logger.error("Payment failed", extra={"order_id": 123, "error": "timeout"})

Уровни логирования:

Уровень Когда использовать Пример
DEBUG Детальная отладка Значения переменных
INFO Нормальная работа Заказ создан, пользователь вошёл
WARNING Необычное, но не критичное Кэш пропущен, медленный запрос
ERROR Ошибка в операции Платёж не прошёл, email не отправлен
КРИТИЧЕСКИЙ Сбой системы База данных недоступна

Circuit Breaker: защита от каскадных сбоев

Circuit breaker — паттерн, который предотвращает повторные запросы к неработающему сервису. Если сервис не отвечает, circuit breaker «открывает» и перестаёт отправлять запросы на заданное время.

Реализация circuit breaker на Python:

import time
from enum import Enum


class CircuitState(Enum):
    CLOSED = "closed"       # Нормальная работа
    OPEN = "open"           # Сервис недоступен, запросы блокируются
    HALF_OPEN = "half_open" # Проверка: один запрос для проверки


class CircuitBreaker:
    def __init__(self, name: str, failure_threshold: int = 5,
                 recovery_timeout: int = 60, half_open_max: int = 1):
        self.name = name
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.half_open_max = half_open_max

        self.state = CircuitState.CLOSED
        self.failure_count = 0
        self.success_count = 0
        self.last_failure_time = None
        self.half_open_requests = 0

    def can_execute(self) -> bool:
        if self.state == CircuitState.CLOSED:
            return True
        if self.state == CircuitState.OPEN:
            if time.time() - self.last_failure_time > self.recovery_timeout:
                self.state = CircuitState.HALF_OPEN
                self.half_open_requests = 0
                return True
            return False
        # HALF_OPEN
        if self.half_open_requests < self.half_open_max:
            self.half_open_requests += 1
            return True
        return False

    def record_success(self):
        if self.state == CircuitState.HALF_OPEN:
            self.success_count += 1
            if self.success_count >= self.half_open_max:
                self.state = CircuitState.CLOSED
                self.failure_count = 0
                self.success_count = 0
        else:
            self.failure_count = 0

    def record_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        if self.state == CircuitState.HALF_OPEN:
            self.state = CircuitState.OPEN
        elif self.failure_count >= self.failure_threshold:
            self.state = CircuitState.OPEN
            logger.warning(
                f"Circuit breaker {self.name} OPEN "
                f"(failures: {self.failure_count})"
            )


# Использование
payment_circuit = CircuitBreaker("payment-service")

@app.post("/orders/pay")
def pay_order(order_id: int):
    if not payment_circuit.can_execute():n        raise HTTPException(
            503, "Payment service temporarily unavailable"
        )
    try:
        result = payment_service.charge(order_id)
        payment_circuit.record_success()
        return result
    except Exception as e:
        payment_circuit.record_failure()
        raise HTTPException(502, "Payment processing failed")

Retry с exponential backoff

Повторные попытки с экспоненциальной задержкой — стандартный подход для временных сбоев.

import time
import random

def retry_with_backoff(func, max_retries: int = 3, base_delay: float = 1.0):
    """Retry с экспоненциальной задержкой и джиттером"""
    for attempt in range(max_retries):
        try:
            return func()
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            # Exponential backoff + jitter
            delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
            logger.warning(
                f"Retry {attempt + 1}/{max_retries} "
                f"after {delay:.1f}s: {e}"
            )
            time.sleep(delay)

# Использование
result = retry_with_backoff(
    lambda: payment_service.charge(order_id),
    max_retries=3,
    base_delay=2.0,
)

Алертинг: когда будить команду

Алертинг — это уведомления о проблемах. Правильный алертинг — когда команда получает только важные уведомления.

Правила алертинга:

  1. Алерт = действие. Если алерт не требует действия — это не алерт, а дашборд.

  2. P0 — немедленно. Сбой сервиса, потеря данных, недоступность базы.

  3. P1 — в течение часа. Деградация производительности, рост ошибок.

  4. P2 — завтра. Предупреждения о приближении лимитов.

Примеры алертов:

Алерт Условие Уровень
Сервис недоступен HTTP 5xx > 50% за 5 минут P0
Высокая latency P95 > 2 секунд за 10 минут P1
Очередь растёт Длина очереди > 1000 за 15 минут P1
Диск заполняется Использование диска > 85% P2
Память Redis Использование > 90% P2

Частые ошибки

  1. Отсутствие мониторинга. Деплой без дашбордов — это полёт вслепую. Мониторинг настраивается до продакшена.

  2. Слишком много алертов. 100 алертов в день — это не мониторинг, это шум. Команда перестает реагировать.

  3. Логи без request_id. Без request_id невозможно отследить запрос через несколько сервисов.

  4. Circuit breaker без half-open. Без half-open состояния circuit breaker никогда не восстановится автоматически.

  5. Retry без backoff. Мгновенные повторные попытки создают лавину запросов к и без того перегруженному сервису.

Заключение

Надёжность системы — это не один инструмент, а комплекс мер: метрики для понимания состояния, логи для отладки, алерты для быстрого реагирования, circuit breaker и retry для устойчивости к сбоям.

Начинайте с простого: Prometheus + Grafana для метрик, структурированные логи в JSON, базовые алерты. Усложняйте по мере роста системы.

Ключевые моменты:

  1. Три столпа наблюдаемости: метрики (Prometheus), логи (JSON), трейсы (OpenTelemetry).

  2. RED-метрики для сервисов, USE-метрики для ресурсов.

  3. Circuit breaker защищает от каскадных сбоев — обязательный паттерн для микросервисов.

  4. Retry с exponential backoff — для временных сбоев, без джиттера.

  5. Алерт = действие. Если алерт не требует реакции — это дашборд, а не алерт.

AI-Помощник