Skip to main content

Troubleshooting Guide

Operational runbook: от жалобы пользователя до root cause.

Когда использовать этот документ

Пользователь написал "не работает" / "не засчиталось" / "не пришло" — открой эту страницу и следуй шагам.

Для изучения архитектуры observability → Observability Для стандартов логирования → Logging Guidelines


1. Quick Start (90% случаев)

Пользователь сообщил о проблеме

Шаг 1: Admin Panel → карточка юзера → кнопка "Логи"

Шаг 2: В Grafana отфильтровать по времени инцидента

Шаг 3: Найти лог с level=error или reason != ""

Шаг 4: Скопировать requestId → найти ВСЕ логи этого запроса

Шаг 5: Прочитать цепочку: вход → бизнес-логика → решение → выход

Root cause найден

2. Decision Tree: Какой инструмент использовать

Когда какой инструмент

СитуацияИнструментЧто делать
Пользователь жалуется "не засчиталось"GrafanaПоиск по userId + время
Поле не приходит на frontend/debug7-layer data flow trace
Ошибка 500 в productionGrafanaПоиск error + requestId
Медленный ответTempoTrace waterfall
Баг не воспроизводится/traceДобавить диагностические логи
Непонятно как работает фича/investigateDeep research по слоям

3. Grafana: Пошаговый flow

Шаг 1: Найти пользователя

Через Admin Panel (рекомендуется):

  1. Открыть админку → найти пользователя по telegramId или имени
  2. Нажать кнопку "Логи" → Grafana откроется с pre-filled запросом

Вручную в Grafana:

{container_name=~".*backend.*"} | json | userId = "USER_UUID_HERE"
userId — это UUID, не telegramId

Если известен только telegramId — сначала найди пользователя в админке, скопируй его UUID.

Шаг 2: Отфильтровать по времени

  1. В Grafana → установи time range на период инцидента
  2. Пользователь обычно помнит "примерно час назад" → ставь ±1 час

Шаг 3: Найти проблемный лог

Ищи ошибки:

{container_name=~".*backend.*"} | json | userId = "UUID" | level >= 40

Ищи отказы по бизнес-правилам:

{container_name=~".*backend.*"} | json | userId = "UUID" | reason != ""

Ищи конкретный endpoint:

{container_name=~".*backend.*"} | json | userId = "UUID" | path = "/api/quests/claim"

Шаг 4: Развернуть контекст запроса

Нашёл подозрительный лог → скопируй requestId:

{container_name=~".*backend.*"} | json | requestId = "abc-123-def"

Это покажет полную цепочку одного HTTP-запроса:

  1. [HTTP] Incoming request — вход
  2. [Domain] ... — бизнес-логика, decision points
  3. [Domain] Claim rejected / logBusinessEvent(...) — решение
  4. [HTTP] Response — выход с statusCode и duration

Шаг 5: Определить root cause

Что видишь в логахRoot causeДействие
reason: "NOT_COMPLETED"Прогресс не достигнутПроверить progress vs targetValue
reason: "ALREADY_CLAIMED"Повторный claimПользователь уже получил награду
reason: "COOLDOWN"Cooldown не прошёлПоказать когда будет доступно
reason: "INSUFFICIENT_BALANCE"Не хватает ресурсовПроверить баланс
level: 50 (error)Exception в кодеСмотреть error.message, stacktrace
statusCode: 500Серверная ошибкаИскать error в цепочке requestId
Нет логов domain-уровняЗапрос не дошёл до сервисаПроверить auth, middleware, route

Шаг 6 (опционально): Tempo trace

Для анализа где тормозит запрос:

  1. Из лога скопируй trace_id (не путать с requestId)
  2. Grafana → Explore → Tempo datasource
  3. Вставь trace_id → waterfall диаграмма

Tempo покажет:

  • Время каждой операции (Prisma запросы, HTTP вызовы)
  • Вложенность операций (span hierarchy)
  • Где именно произошла задержка
Корреляция Tempo ↔ Loki

В Grafana настроена связь: клик на span в Tempo → переход к логам с этим requestId.


4. LogQL Cheat Sheet

По типу проблемы

Квест не засчитался
# Все quest-related логи пользователя
{container_name=~".*backend.*"} |= "[Quest]" | json | userId = "UUID"

# Отказы по квестам
{container_name=~".*backend.*"} |= "Claim rejected" | json | userId = "UUID"

# Прогресс квестов
{container_name=~".*backend.*"} |= "progress" | json | userId = "UUID"
Награда не пришла (XP, Scrap)
# Бизнес-события экономики пользователя
{container_name=~".*backend.*"} |= "[BUSINESS]" | json | userId = "UUID"

# Все economy события
{container_name=~".*backend.*"} | json | eventType =~ "economy.*" | userId = "UUID"

# Конкретно scrap
{container_name=~".*backend.*"} | json | eventType = "economy.scrap_earned" | userId = "UUID"
Кейс не открылся
# Case opening логи
{container_name=~".*backend.*"} |= "[Case]" | json | userId = "UUID"

# Ошибки на endpoint
{container_name=~".*backend.*"} | json | path =~ "/api/cases.*" | userId = "UUID" | level >= 40
Referral проблемы
# Referral логи пользователя
{container_name=~".*backend.*"} |= "[Referral]" | json | userId = "UUID"

# Passive income
{container_name=~".*backend.*"} |= "passive" | json | userId = "UUID"

# Активация реферала
{container_name=~".*backend.*"} |= "activate" |= "[Referral]" | json
Webhook от Rust сервера
# Все webhook логи
{container_name=~".*backend.*"} | json | path =~ "/webhook.*"

# Webhook ошибки
{container_name=~".*backend.*"} | json | path =~ "/webhook.*" | level >= 50

# Конкретный тип события
{container_name=~".*backend.*"} |= "PLAYER_KILL" | json
Промокод не сработал
# Promo code логи
{container_name=~".*backend.*"} |= "[Promo]" | json | userId = "UUID"

# Отказы
{container_name=~".*backend.*"} | json | path = "/api/promo-codes/redeem" | userId = "UUID"

Общие запросы

# Все ошибки 500 за последний час
{container_name=~".*backend.*"} | json | statusCode = 500

# Медленные запросы (>2 секунды)
{container_name=~".*backend.*"} | json | duration > 2000

# Все отказы по бизнес-правилам
{container_name=~".*backend.*"} | json | reason != ""

# Бизнес-события определённого домена
{container_name=~".*backend.*"} |= "[BUSINESS]" | json | eventType =~ "quests.*"

# Поиск по тексту ошибки
{container_name=~".*backend.*"} |= "PrismaClientKnownRequestError"

# Rate of errors (для dashboards)
rate({container_name=~".*backend.*"} | json | level >= 50 [5m])

Reason Codes — что означает каждый

Reason CodeЧто произошлоЭто баг?
NOT_COMPLETEDПрогресс не достигнут (5/10)Нет — пользователь не выполнил условие
ALREADY_CLAIMEDНаграда уже полученаНет — повторный запрос
COOLDOWNНе прошёл cooldown (spin, passive income)Нет — нужно подождать
NOT_FOUNDСущность не найденаВозможно — проверить ID
INSUFFICIENT_BALANCEНе хватает scrap/SPНет — недостаточно средств
INVALID_STATEНеверное состояние (withdrawal, quest)Возможно — проверить state machine
SEASON_MISMATCHРазные сезоныНет — данные из прошлого сезона
LIMIT_REACHEDДостигнут лимитНет — системное ограничение
SELF_REFERRALПопытка саморефаНет — антифрод
EXPIREDИстёк срок (промокод, сессия)Нет — время вышло
Reason ≠ Bug

Большинство reason codes — это нормальное поведение системы. Баг — когда reason не соответствует реальности (прогресс 10/10, а reason = NOT_COMPLETED).


5. Escalation: когда логов недостаточно

Уровень 1: /debug — Data Flow Trace

Когда: Поле = null / 0 / не приходит на frontend, но в БД данные есть.

/debug "баффы не показывают tier"

/debug проверяет 7 слоёв в порядке частоты багов:

#Слой% баговЧто проверяет
1Fastify Schema45%Поле есть в response schema?
2Controller20%Поле передаётся в response?
3Prisma Query15%Поле в select/include?
4Shared Types8%Тип синхронизирован?
5Service Return5%Return включает поле?
6Frontend4%Правильный путь к данным?
7Zod Validation3%.strict() не отбрасывает?

Результат: Точный файл:строка где данные "теряются".

Уровень 2: /trace — Диагностические логи

Когда: /debug не нашёл проблему в коде, нужно видеть runtime данные.

/trace "quest reward не начисляется"

/trace добавляет маркированные logger.debug('[TRACE:ID]') в критичные точки. После деплоя — воспроизвести проблему и найти в Grafana:

{container_name=~".*backend.*"} |= "TRACE:a1b2c3"

После решения: обязательно удалить trace-логи:

/trace --clean

Уровень 3: /investigate — Deep Research

Когда: Нужно понять архитектуру прежде чем искать баг.

/investigate --flow "webhook → quest progress → frontend"
/investigate --impact "удалить поле X"

Уровень 4: /db — Прямой запрос к БД

Когда: Нужно проверить состояние данных конкретного пользователя.

/db "квесты пользователя UUID"
/db "passive income balance для UUID"

6. Типичные проблемы и их сигнатуры

"Квест не засчитывается"

Сигнатура в логах:

[Quest] Claim rejected { reason: "NOT_COMPLETED", current: 5, required: 10 }

Checklist:

  1. Прогресс действительно не достигнут? → Проверить current vs required
  2. Progress обновляется? → Искать [Quest] Progress updated для этого userId
  3. Webhook приходит? → Искать path =~ "/webhook.*" с нужным eventType
  4. Правильный serverId? → Проверить что сервер привязан к пользователю

"Награда не пришла"

Сигнатура в логах:

[BUSINESS] QUEST_REWARD_CLAIMED { userId, questId, rewardType, amount }

Если [BUSINESS] лог есть, но баланс не изменился:

  1. Проверить транзакцию → была ли ошибка после event?
  2. Проверить Prisma query → $transaction завершился?

Если [BUSINESS] лога нет:

  1. Искать reason в warn логах
  2. Искать error в цепочке requestId

"Ошибка 500"

Сигнатура в логах:

[HTTP] Response { statusCode: 500, duration: 45, path: "/api/...", requestId: "..." }

Всегда:

  1. Скопировать requestId
  2. Найти все логи этого request — среди них будет level: 50 с error.message
  3. Типичные причины:
    • PrismaClientKnownRequestError → проблема с БД запросом
    • TypeError: Cannot read properties of null → данные не найдены, нет проверки
    • ETIMEDOUT → внешний сервис не отвечает

"Баффы / предметы не отображаются"

Скорее всего /debug проблема, а не лог-проблема:

  • 45% случаев — поле не описано в Fastify response schema
  • 20% — controller не передаёт поле
  • 15% — Prisma select/include не включает relation
/debug "баффы не показывают tier"

"Webhook не обрабатывается"

Сигнатура в логах:

{container_name=~".*backend.*"} | json | path =~ "/webhook.*" | level >= 40

Checklist:

  1. Webhook вообще приходит? → Искать [HTTP] Incoming request на /webhook
  2. Валидация прошла? → Искать [RustIntegration] логи
  3. Идемпотентность → Повторный webhook? Проверить RustWebhookLog

7. Инфраструктурные проблемы

Логи не появляются в Grafana

  1. Приложение не выводит логи?

    • Проверить LOG_LEVEL в Dokploy → Environment Variables
    • LOG_LEVEL=info не покажет debug логи
  2. Promtail не работает?

    docker logs promtail
  3. Loki не принимает?

    curl -s http://localhost:3100/ready
  4. Docker socket?

    docker exec promtail ls -la /var/run/docker.sock

Подробнее: Log Architecture → Troubleshooting

Нужно временно включить debug логи

  1. В Dokploy: изменить LOG_LEVEL=debug
  2. Перезапустить контейнер
  3. Воспроизвести проблему
  4. Не забыть вернуть LOG_LEVEL=info после диагностики

Логи старше 7 дней

Loki хранит логи 7 дней. Если проблема произошла раньше — логов уже нет.

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

  • Docker logs на сервере (если не ротированы)
  • git log для анализа что менялось в коде в тот период

8. Reference

Grafana Access

РесурсURLДоступ
Grafana UIhttps://grafana.goloot.onlineЛогин/пароль (Traefik + SSL)
Grafana APIhttps://grafana.goloot.online/api/Service Account Token (Bearer)
PrometheusТолько внутри Docker (prometheus:9090)Через Grafana datasource proxy
LokiТолько внутри Docker (loki:3100)Через Grafana datasource proxy
TempoТолько внутри Docker (tempo:3200)Через Grafana datasource proxy
Нет прямых портов

Prometheus, Loki, Tempo не имеют внешних портов. Все запросы — через Grafana UI или Grafana API (datasource proxy).

Grafana API (для автоматизации)

Grafana Service Account с ролью Viewer позволяет запрашивать все datasource через API без UI:

# Prometheus метрики
curl -H "Authorization: Bearer glsa_xxx" \
"https://grafana.goloot.online/api/datasources/proxy/uid/prometheus/api/v1/query?query=cache_hit_total"

# Loki логи (последний час)
curl -G -H "Authorization: Bearer glsa_xxx" \
"https://grafana.goloot.online/api/datasources/proxy/uid/loki/loki/api/v1/query_range" \
--data-urlencode 'query={container=~".*backend.*"} | json | level >= 50' \
--data-urlencode "start=$(date -d '1 hour ago' +%s)" \
--data-urlencode "end=$(date +%s)" --data-urlencode "limit=10"

# Статус всех Prometheus targets
curl -H "Authorization: Bearer glsa_xxx" \
"https://grafana.goloot.online/api/datasources/proxy/uid/prometheus/api/v1/targets"

Создание токена: Grafana → Administration → Service Accounts → New (Viewer) → Generate Token. Подробнее: Monitoring Setup → Grafana API.

Datasources в Grafana

SourceUIDНазначениеКогда использовать
LokilokiЛогиПоиск по userId, requestId, reason
PrometheusprometheusМетрикиDashboards, rate of errors, latency, cache stats
TempotempoTracesWaterfall запроса, bottleneck analysis

Связанные документы

ДокументЧто покрывает
ObservabilityАрхитектура: Logger API, Metrics, Tracing, Business Events
Log ArchitectureПайплайн: Pino → Docker → Promtail → Loki → Grafana
Logging GuidelinesСтандарты: формат, domain tags, reason codes
Monitoring READMEQuick start мониторинг-стека

Claude Skills для troubleshooting

SkillКогда использовать
/debug "симптом"Поле не приходит / null / 0 — проверка 7 слоёв
/trace "проблема"Нужны runtime-данные — добавление диагностических логов
/investigate "вопрос"Нужно понять архитектуру — deep research
/db "запрос"Проверить данные в БД напрямую