Legan Studio
Все статьи
~ 17 мин чтения

Логирование и алерты для бота в MAX

Как организовать логи и алерты для бота MAX в продакшене: структурное логирование, метрики, корреляция запросов, дежурные каналы.

  • MAX
  • разработка
  • мониторинг

В разработке логи нужны для отладки, в проде — для расследования инцидентов. Бот, у которого нет нормального логирования, превращается в чёрный ящик: пользователь жалуется, но воспроизвести нельзя, потому что нет следов. Хорошая система логов и алертов экономит часы при разборе проблем и сокращает простои. У бота в MAX нет UI на стороне сервера, нет HTTP-ответа, нет консоли разработчика у пользователя — вся диагностика идёт через логи, трекер ошибок и метрики. В этой статье разберём, как поставить наблюдаемость с нуля: структурные логи, корреляционные ID, Sentry с фильтрацией ПДн, Prometheus, дашборды Grafana, алерты и on-call процесс.

Зачем структурированные логи

Текстовая строка INFO: user 123 pressed button удобна глазами, но в проде с тысячами апдейтов в минуту это бесполезный шум. Структурный JSON-лог решает три задачи.

Поиск. JSON с полями event, user_id, chat_id позволяет в Loki или Kibana за секунду собрать все события одного пользователя или когорты. С текстом — регулярки на гигабайтах данных.

Фильтрация и агрегация. Сколько раз за час падал handler process_payment? Это запрос count by (event) where event="payment_failed", а не grep + wc + ручной разбор.

Алерты. Если поле level=ERROR приходит чаще порога — отправить дежурному. На текстовых логах такой алерт не написать: кто-то закоммитит print("ERROR in test") и сломает счётчик.

Структурные логи — это контракт между разработчиком и системой мониторинга. Один раз договорились о схеме полей — дальше алерты и дашборды работают сами.

Уровни: когда что писать

Стандартные уровни (DEBUG, INFO, WARNING, ERROR, CRITICAL) — это договор о шуме. У каждого своя аудитория и свой ретеншн.

  • DEBUG — подробности для разбора инцидента: каждое API-сообщение от MAX, состояние FSM, тело апдейта. В проде по умолчанию выключен, включается выборочно для одного пользователя или хендлера.
  • INFO — бизнес-события: новый пользователь, оформление заказа, успешный платёж, прохождение квиза. Это «история бизнеса», которую захочется поднять через год для аналитики.
  • WARNING — что-то пошло не так, но бот выкрутился: ретрай оплаты, фоллбек на дефолтный ответ, превышен soft-лимит, медленный ответ внешнего API.
  • ERROR — исключения и неожиданное поведение, требующее реакции. Каждый ERROR — потенциальный пост-мортем.
  • CRITICAL — отказ ключевых компонентов: упал Redis, недоступен MAX Bot API, не отвечает база. Будит дежурного ночью.

Главная ошибка — лить INFO как DEBUG. Если в INFO попадает «вошёл в middleware», «вышел из middleware», «отправил запрос», аналитика превращается в кашу, а бюджет хранения уходит в трубу.

Сравнение библиотек логирования (Python)

БиблиотекаСтруктурные логиJSON из коробкиКонтекстные переменныеПроизводительность
logging (stdlib)Нет, через формат-строкиЧерез python-json-loggerНет, нужен contextvars рукамиБазовая
structlogДа, нативноЧерез JSONRendererДа, bind_contextvarsВысокая
loguruЧерез extra={...}Через serialize=TrueЧерез contextualizeСредняя

Для бота в MAX рекомендуем structlog: спроектирован под структурные логи, имеет хорошую интеграцию с contextvars и нормально дружит со стандартным logging (можно перенаправить логи MAX-SDK через стандартный модуль).

Конфигурация structlog

Минимальный продакшен-конфиг:

import logging
import structlog
from structlog.contextvars import merge_contextvars

logging.basicConfig(format="%(message)s", level=logging.INFO)

structlog.configure(
    processors=[
        merge_contextvars,
        structlog.processors.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.JSONRenderer(),
    ],
    wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
    logger_factory=structlog.PrintLoggerFactory(),
    cache_logger_on_first_use=True,
)

log = structlog.get_logger()
log.info("bot_started", version="1.4.2", env="prod", platform="max")

На выходе — однострочный JSON, готовый для Loki или Yandex Cloud Logging. merge_contextvars подмешивает в каждое сообщение поля, выставленные через bind_contextvars(user_id=...) — так передаётся trace_id через все слои без явного аргумента.

Корреляционные ID

Один пользовательский апдейт может проходить через несколько сервисов: бот в MAX → CRM → платёжка → бот. Без сквозного request_id или trace_id собрать всю цепочку в одно расследование невозможно.

При получении апдейта генерируем UUID и пробрасываем в каждый исходящий вызов через заголовок X-Request-Id. Все сервисы логируют его. В централизованном хранилище фильтруем по request_id и видим всю историю.

Минимальный набор полей в каждой записи лога:

  • trace_id — UUID на апдейт.
  • update_id — числовой ID апдейта от MAX.
  • user_id — числовой ID пользователя.
  • chat_id — числовой ID чата (для групп отличается от user_id).
  • command или handler — какой handler обработал апдейт.
  • bot_version — версия деплоя.

С такими полями вся история обработки апдейта поднимается одним фильтром trace_id="abc-123", а не разбором пяти сервисов руками. Полноценный distributed tracing (OpenTelemetry, Jaeger) — следующий уровень.

Что логировать, а что нет

Логируем:

  • Каждый входящий апдейт от MAX (тип, user_id, chat_id).
  • Каждый исходящий вызов API (метод, latency, статус).
  • Каждый переход FSM.
  • Каждое создание/изменение бизнес-сущности (заявка, заказ, платёж).
  • Все ошибки со стектрейсом.

НЕ логируем:

  • Полные тексты сообщений (особенно с ПДн).
  • Пароли, токены, ключи в любых вариантах.
  • Полные тела платёжных webhook (там номера карт, хоть и маскированные).

При сомнениях — логируйте маску («введён номер карты ***1234») вместо полного значения. Утечка через логи — частая причина инцидентов с ПДн.

Sentry: что это и зачем

Логи отвечают на «что происходило». Sentry (и аналоги — GlitchTip, Bugsnag, Rollbar) отвечает на «что сломалось и где». Это специализированный трекер ошибок: группирует одинаковые исключения по fingerprint, показывает стектрейс с переменными, ведёт счётчик «сколько пользователей затронуто», строит графики деградации, шлёт алерты в MAX-канал команды или Slack.

Минимальная интеграция:

  1. SDK инициализируется на старте.
  2. Любое необработанное исключение в обработчике летит в Sentry с трейсом.
  3. К каждому событию прикрепляются user_id, update_id, command.
  4. Алерты в MAX-канал команды на новые ошибки и регрессии.

Sentry init для бота в MAX

import logging
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.asyncio import AsyncioIntegration

sentry_sdk.init(
    dsn="https://example@o0.ingest.sentry.io/0",
    environment="prod",
    release="max-bot@1.4.2",
    traces_sample_rate=0.2,
    profiles_sample_rate=0.1,
    send_default_pii=False,
    integrations=[
        LoggingIntegration(level=logging.INFO, event_level=logging.ERROR),
        AsyncioIntegration(),
    ],
    before_send=scrub_pii,
)

release в формате имя@версия нужен Sentry для функции «эта ошибка появилась в релизе X» — критично при поиске регрессий после деплоя. send_default_pii=False отключает автоматическое прикрепление IP и заголовков. before_send — функция-фильтр, через которую SDK прогоняет каждое событие перед отправкой.

Breadcrumbs — это «след» событий, который Sentry прикрепляет к ошибке. Когда что-то сломается, в карточке Sentry будут видны последние 50 действий: какой handler вошёл, какой запрос ушёл в БД, какой ответ пришёл от внешнего API.

from sentry_sdk import set_user, set_tag, add_breadcrumb, capture_exception

async def sentry_middleware(handler, event, data):
    user = data.get("event_from_user")
    if user:
        set_user({"id": user.id})
    set_tag("update_id", event.update_id)
    set_tag("platform", "max")
    add_breadcrumb(
        category="max-bot",
        message=f"handler:{handler.__name__}",
        level="info",
    )
    try:
        return await handler(event, data)
    except Exception as exc:
        capture_exception(exc)
        raise

set_user привязывает событие к пользователю — в карточке Sentry будет «затронуто N пользователей», а не «N событий». set_tag добавляет фасет, по которому удобно фильтровать в UI.

Sensitive data scrubbing

Главная ошибка — логировать тело сообщений «для удобства». В чатах боту в MAX прилетают телефоны, паспорта, медицинские данные, банковские реквизиты. Эти данные не должны лежать в Loki, Sentry или Datadog. Это требование 152-ФЗ и GDPR, а ещё репутационный риск: утечка логов с ПДн = потенциально многомиллионный штраф.

Правила:

  1. Не логировать полный текст сообщения пользователя.
  2. Маскировать телефоны, email, карты: +7***4567, i***@gmail.com, 4242 **** **** 1234.
  3. Не сохранять тело платёжных webhook, если в нём детали карты.
  4. Конфигурировать Sentry before_send для удаления чувствительных полей.
  5. Для медицинских и финансовых ботов — отдельный аудит логов раз в квартал.

Пример скрабера для Sentry:

import re

PHONE_RE = re.compile(r"(\+?\d{1,3})\d{6,10}(\d{2,4})")
EMAIL_RE = re.compile(r"([\w.-]+)@([\w.-]+)")
CARD_RE = re.compile(r"\b(\d{4})\d{8,12}(\d{4})\b")

def scrub_pii(event, hint):
    def mask(s: str) -> str:
        s = PHONE_RE.sub(r"\1***\2", s)
        s = EMAIL_RE.sub(r"\1***@\2", s)
        s = CARD_RE.sub(r"\1********\2", s)
        return s

    if msg := event.get("message"):
        event["message"] = mask(msg)
    for exc in event.get("exception", {}).get("values", []):
        if exc_msg := exc.get("value"):
            exc["value"] = mask(exc_msg)
    return event

Если для отладки нужно тело сообщения — только в DEBUG и отдельным потоком, не уходящим в облако.

Sampling: errors 100%, traces 10-30%

Sentry берёт деньги за events. На активном боте легко улететь в десятки тысяч долларов в год, если отправлять каждый запрос как transaction.

  • Errors — 100%. Каждое исключение должно дойти до Sentry.
  • Traces (performance) — 10-30%. Достаточно, чтобы видеть p95/p99 латентности.
  • Profiling — 5-10% от traces. Сэмплы профилировщика тяжёлые, держите долю низкой.

Можно настроить динамический sampler: 100% для редких хендлеров, 5% для горячих. SDK поддерживает traces_sampler, который получает контекст и возвращает долю.

Альтернативы Sentry

СервисМодельПлюсыМинусы
Sentry CloudSaaS, freemiumЛучший UX, интеграцииДорого на масштабе
GlitchTipSelf-hosted, OSSAPI-совместим с Sentry, бесплатноНет profiling, slimmer UI
Sentry Self-hostedOSS, on-premПолный функционал, контроль данныхТяжёлая инсталляция (Postgres + Kafka + ClickHouse)
BugsnagSaaSХорошие release dashboardsДороже Sentry
RollbarSaaSRQL-запросы по событиямСлабее интеграций

Для бота в MAX в РФ-контуре оптимально: GlitchTip self-hosted, если важна цена и локализация ПДн, или Sentry Cloud, если важна скорость старта. Bugsnag и Rollbar не подходят под требования 152-ФЗ по локализации.

Логи в файл vs stdout

Старая школа — писать в файл с rotating handler:

from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
    "/var/log/max-bot.log",
    maxBytes=50_000_000,
    backupCount=10,
)

Современная (cloud-native) — писать в stdout, а сбор и ротацию делегировать Docker, systemd-journald или сборщику:

  • Stateless контейнер, без mounted volume под логи.
  • Стандартный pipeline: docker logs, kubectl logs, journalctl -u max-bot.service.
  • Логи попадают в централизованное хранилище без участия приложения.
  • Перезапуск контейнера не теряет логи (если есть сборщик).

Для Docker-деплоя — только stdout/stderr. Для сервера на metal или systemd — допустимо в файл, но проще оставить journald.

Сбор логов: куда отправлять

СтекТранспортХранилищеUIКогда выбирать
Loki + PromtailgRPCObject storage (S3)GrafanaДешёвое хранение, нет full-text search
ELKFilebeat / LogstashElasticsearchKibanaНужен полнотекстовый поиск
Datadog LogsAgentCloudDatadogSaaS «всё в одном», дорого
Yandex Cloud LoggingFluent Bit / SDKОблакоCloud ConsoleДеплой в Yandex Cloud, требования 152-ФЗ
Vector + ClickHouseVectorClickHouseGrafana / MetabaseHigh-throughput, контроль расходов

Для бота в MAX на VPS в России — Loki + Promtail + Grafana или Yandex Cloud Logging. ELK перебор, Datadog слишком дорогой и не российский.

Метрики: что снимать

Логи и Sentry — для разбора инцидента после факта. Метрики — для проактивного мониторинга. Минимум для бота в MAX:

  • bot_updates_total — счётчик апдейтов по типу (message, callback, command).
  • bot_handler_latency_seconds — гистограмма времени обработки.
  • bot_api_call_duration_seconds — длительность вызовов MAX API.
  • bot_external_api_duration_seconds — длительность вызовов внешних сервисов (CRM, банк, МИС).
  • bot_errors_total — счётчик ошибок по типу.
  • Лаг long polling или время доставки webhook.
  • Доля 429 (rate limit) от MAX.

Prometheus + Grafana — отраслевой стандарт. Алерт на «доля ошибок выше 1% за 5 минут» уведомит дежурного раньше, чем пользователь напишет в поддержку.

Prometheus metrics в боте

from prometheus_client import Counter, Histogram, start_http_server

UPDATES = Counter(
    "bot_updates_total",
    "Total updates received from MAX",
    ["handler", "status"],
)
LATENCY = Histogram(
    "bot_handler_latency_seconds",
    "Handler latency",
    ["handler"],
    buckets=(0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10),
)

async def metrics_middleware(handler, event, data):
    name = handler.__name__
    with LATENCY.labels(handler=name).time():
        try:
            result = await handler(event, data)
            UPDATES.labels(handler=name, status="ok").inc()
            return result
        except Exception:
            UPDATES.labels(handler=name, status="error").inc()
            raise

start_http_server(9090)

Prometheus периодически опрашивает :9090/metrics, складывает в TSDB, Grafana строит графики. Алертинг через Alertmanager: правило rate(bot_updates_total{status="error"}[5m]) > 0.01 шлёт алерт.

Health-checks

Liveness и readiness — два разных вопроса. Liveness отвечает «процесс жив», readiness — «процесс готов принимать трафик». В MAX-боте liveness проверяем эквивалентом getMe (запрос к MAX API на проверку токена), readiness — доступностью БД и Redis.

from aiohttp import web

async def healthz(request):
    return web.Response(text="ok")

async def readyz(request):
    try:
        await max_client.get_me()  # liveness MAX API
        await db.execute("SELECT 1")  # liveness БД
        await redis.ping()  # liveness Redis
        return web.Response(text="ready")
    except Exception as exc:
        return web.Response(status=503, text=str(exc))

app = web.Application()
app.router.add_get("/healthz", healthz)
app.router.add_get("/readyz", readyz)

Kubernetes использует эти эндпоинты для рестартов и роутинга трафика. Docker Compose — для healthcheck-условий зависимостей.

Distributed tracing

Если бот ходит в несколько микросервисов (CRM, оплата, склад, ML-инференс), без distributed tracing невозможно понять, где тормозит запрос. Стандарт — OpenTelemetry: SDK инжектит traceparent в HTTP-заголовки, бэкенды его подхватывают, и в Jaeger / Tempo / Sentry виден полный span-tree от MAX-апдейта до ответа БД.

Минимум — настроить экспортёр OTLP в Tempo или Jaeger, обернуть HTTP-клиент в инструментацию (opentelemetry-instrumentation-aiohttp-client), и автоматически каждый запрос будет трассироваться. Sentry поддерживает OpenTelemetry как источник span-ов.

Алерты

Хороший алерт — это:

  • Действенный — на него можно реагировать.
  • Точный — не сработает на ровном месте.
  • Срочный — требует внимания сейчас, а не «когда-то».

Базовый набор технических алертов:

  1. Бот не отвечает — нет успешных апдейтов от MAX более 5 минут.
  2. Ошибки в API MAX — 5xx или 429 чаще порога.
  3. Падение внешних интеграций — CRM/банк/МИС не отвечают.
  4. Нагрузка — очередь апдейтов растёт (long polling) или 5xx на webhook.
  5. Ошибки бизнес-логики — массовые исключения в одном сценарии.

Алерты идут в дежурный канал — отдельный чат в MAX, Slack или корпоративная почта. Главное — чтобы их видели, а не молча копили.

Алерты на бизнес-метрики

Технические алерты необходимы, но недостаточны. Бот может работать без ошибок и при этом не приносить деньги — например, поломалась интеграция с CRM, и лиды не доходят. Технически всё ОК, бизнес стоит.

Алерты на бизнес-метрики:

  • Конверсия квиза упала ниже X% за час.
  • Платежи не проходят более N минут подряд.
  • Резко выросла доля брошенных корзин.
  • Количество новых пользователей упало в два раза против скользящей средней.
  • Среднее время до первого платежа выросло на N процентов.

Это пишется в Grafana или Yandex Cloud Monitoring как production alerts уровня бизнеса. Срабатывает реже технических, но каждое срабатывание — реальные деньги.

Дашборды для дежурного

Не «50 графиков», а 5–7 ключевых:

  • Активные пользователи (5/30/60 минут).
  • Error rate по сценариям.
  • Latency p50/p95/p99 на обработку.
  • API health (MAX, внешние интеграции).
  • Очередь и нагрузка процессоров.

Дашборд должен открываться по ссылке за секунду, без логина и фильтров.

On-call процесс

Алерт без процесса — это просто шум в чате. Минимальный on-call процесс для команды из 2-5 разработчиков:

  1. Расписание дежурств: одна неделя на человека.
  2. Канал алертов в MAX или Slack, куда летят CRITICAL и ERROR с пороговыми условиями.
  3. Эскалация: если дежурный не отреагировал за 15 минут — уведомление лидеру.
  4. Runbook: на каждый частый алерт — короткая инструкция «что проверить, что сделать».
  5. Пост-мортем: после каждого инцидента — заметка в notion с timeline и action items.

Инструменты: PagerDuty (стандарт), OpsGenie (дешевле), Grafana OnCall (OSS) или собственный MAX-бот для дежурного, который пингует и эскалирует через 15 минут.

152-ФЗ и GDPR в логах

Логи — это «обработка персональных данных». Если в логе лежит user_id MAX, это уже ПДн (косвенный идентификатор). Если телефон, email, ФИО — прямые ПДн.

Требования:

  • В уведомлении Роскомнадзора (152-ФЗ) указать, что логи — часть обработки ПДн.
  • Логи не должны храниться дольше срока, нужного для целей обработки. Стандарт — 30-90 дней для оперативных, 1 год для аудита.
  • Доступ к логам — только разработчикам, с журналированием доступа.
  • При запросе пользователя «удалите мои данные» — удалить и логи. Технически: либо retention достаточно короткий, либо специальный pipeline зачистки по user_id.
  • При трансграничной передаче (Sentry Cloud в США / ЕС) — отдельное уведомление РКН по ст. 12 152-ФЗ.

Безопасный путь — self-hosted Sentry или GlitchTip в российском контуре + Loki в Yandex Cloud, без выезда ПДн за границу.

Расследование инцидента

Сценарий: «у пользователя X не прошла оплата». Шаги:

  1. По user_id ищем последние апдейты в логах.
  2. По trace_id поднимаем всю цепочку.
  3. Проверяем ответ платёжного сервиса.
  4. Сверяем с метриками: была ли деградация в этот момент.
  5. Если ошибка повторяется у других — разбор инцидента и патч.

Без структурных логов и trace_id это занимает часы. С ними — минуты.

Чек-лист перед продом

  1. Структурные JSON-логи с уровнями.
  2. Sentry с фильтром ПДн в before_send.
  3. trace_id во всех слоях через contextvars.
  4. Метрики латентности, ошибок и очередей в Prometheus.
  5. Health-чек /healthz и /readyz.
  6. Алерты в MAX-канал команды на критичные события.
  7. Алерты на бизнес-метрики (конверсия, платежи).
  8. Ретеншн логов 30-90 дней и архив холодных логов.
  9. Доступ к логам и Sentry — только разработчикам, с журналированием.
  10. Runbook на каждый частый алерт, расписание on-call.

Итого

Логи и алерты — основа эксплуатации бота в MAX. Структурное логирование с trace_id, Sentry с фильтром ПДн, метрики Prometheus, дашборды Grafana и осмысленный набор алертов закрывают большую часть инцидентов. Они становятся не блокером, а тематикой 5-минутного разбора. На старте можно обойтись простым stdout, но в проде эта инфраструктура окупается с первого же серьёзного бага. Скрабинг ПДн перед отправкой в облако — требование 152-ФЗ и GDPR, а не «приятное дополнение». Настройка занимает 1-3 дня, поддержка после — около часа в неделю на чистку шума и обновление дашбордов.

Частые вопросы

Что такое структурное логирование и зачем оно боту MAX?

Это формат логов как JSON с контекстными полями вместо текстовых строк. Текстовый лог «INFO: user pressed button» бесполезен в проде. Структурный JSON со всеми полями (level, timestamp, msg, user_id, from_state, to_state, trace_id) позволяет фильтровать, агрегировать, строить дашборды. Библиотеки: structlog, loguru (Python), pino, winston (Node), zerolog, zap (Go). Все умеют JSON-вывод с минимальным оверхедом. Без структуры в первом инциденте придётся грепать тысячи строк, и это дорого.

Как корректировать запросы между сервисами в боте MAX?

Через сквозной request_id или trace_id. Один пользовательский апдейт может проходить через несколько сервисов: бот → CRM → платёжка → бот. Без trace_id собрать всю цепочку невозможно. При получении апдейта от MAX генерируем UUID и пробрасываем в каждый исходящий вызов через заголовок X-Request-Id. Все сервисы логируют его. В централизованном хранилище фильтруем по trace_id и видим всю историю. Полноценный distributed tracing (OpenTelemetry, Jaeger) — следующий уровень, для большинства ботов хватает trace_id через contextvars.

Что нужно логировать в боте MAX, а что нельзя?

Логируем: каждый входящий апдейт от MAX (тип, user_id, chat_id), каждый исходящий вызов API (метод, latency, статус), каждый переход FSM, каждое создание/изменение бизнес-сущности (заявка, заказ, платёж), все ошибки со стектрейсом. Не логируем: полные тексты сообщений (особенно с ПДн), пароли, токены, ключи в любых вариантах, полные тела платёжных webhook (номера карт, хоть и маскированные). При сомнениях — логируйте маску (введён номер карты ***1234) вместо полного значения. Утечка через логи — частая причина инцидентов с ПДн.

Как настроить Sentry для бота в MAX?

Установить sentry-sdk, инициализировать на старте с DSN, environment, release в формате name@version, traces_sample_rate 0.1-0.3, send_default_pii False, before_send со скрабером ПДн. Подключить LoggingIntegration и AsyncioIntegration. В middleware бота на входе вызвать set_user из event.from_user, set_tag update_id, add_breadcrumb с именем хендлера. На исключении — capture_exception и raise дальше. release нужен Sentry для определения регрессий после деплоя. Errors отправляем 100%, traces 10-30% — иначе бюджет улетит на масштабе.

Где хранить логи бота MAX в продакшене?

Четыре варианта. Loki + Promtail + Grafana — лёгкое, индексирует по меткам, дешёвое в эксплуатации, рекомендуемый компромисс. ELK (Elasticsearch + Kibana) — мощное, но прожорливое по ресурсам, оправдано на больших объёмах с full-text search. Yandex Cloud Logging — управляемый сервис, не нужно держать стек, удобно для РФ-проектов и требований 152-ФЗ. Локальный stdout + ротация — минимальный вариант для маленьких ботов на старте. Для прода рекомендуется Loki или Yandex Cloud Logging. Datadog слишком дорогой и не российский.

Какие метрики и алерты нужны для бота MAX?

Минимум технических метрик: апдейты в секунду, латентность p50/p95/p99, доля ошибок на 1000 апдейтов, длина очереди задач, лаг long polling, доля 429 от MAX, количество вызовов MAX API по методу. Стек — Prometheus + Grafana с Alertmanager. Алерт на «доля ошибок выше 1% за 5 минут» уведомит дежурного раньше пользователя. Бизнес-метрики тоже критичны: конверсия квиза, доля прошедших платежей, новые пользователи в час. Бот может технически работать, но бизнес стоять — например, упала интеграция с CRM. Алерты на бизнес-метрики срабатывают реже, но всегда означают реальные деньги.

Какие альтернативы Sentry для self-hosted сценария?

GlitchTip — open-source трекер ошибок, API-совместим с Sentry SDK, ставится из docker-compose за 15 минут. Не имеет profiling и session replay, но для большинства MAX-ботов хватает. Sentry Self-hosted — полный функционал, но требует Postgres, Kafka, ClickHouse, Redis и минимум 8 ГБ RAM на сервер. Bugsnag и Rollbar — SaaS-альтернативы Sentry Cloud, не подходят для требований по локализации ПДн в 152-ФЗ. Для российских проектов оптимально GlitchTip в собственном контуре или Sentry Self-hosted на отдельной VM.