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 | /debug | 7-layer data flow trace |
| Ошибка 500 в production | Grafana | Поиск error + requestId |
| Медленный ответ | Tempo | Trace waterfall |
| Баг не воспроизводится | /trace | Добавить диагностические логи |
| Непонятно как работает фича | /investigate | Deep research по слоям |
3. Grafana: Пошаговый flow
Шаг 1: Найти пользователя
Через Admin Panel (рекомендуется):
- Открыть админку → найти пользователя по telegramId или имени
- Нажать кнопку "Логи" → Grafana откроется с pre-filled запросом
Вручную в Grafana:
{container_name=~".*backend.*"} | json | userId = "USER_UUID_HERE"
Если известен только telegramId — сначала найди пользователя в админке, скопируй его UUID.
Шаг 2: Отфильтровать по времени
- В Grafana → установи time range на период инцидента
- Пользователь обычно помнит "примерно час назад" → ставь ±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-запроса:
[HTTP] Incoming request— вход[Domain] ...— бизнес-логика, decision points[Domain] Claim rejected/logBusinessEvent(...)— решение[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
Для анализа где тормозит запрос:
- Из лога скопируй
trace_id(не путать сrequestId) - Grafana → Explore → Tempo datasource
- Вставь
trace_id→ waterfall диаграмма
Tempo покажет:
- Время каждой операции (Prisma запросы, HTTP вызовы)
- Вложенность операций (span hierarchy)
- Где именно произошла задержка
В 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 codes — это нормальное поведение системы. Баг — когда reason не соответствует реальности (прогресс 10/10, а reason = NOT_COMPLETED).
5. Escalation: когда логов недостаточно
Уровень 1: /debug — Data Flow Trace
Когда: Поле = null / 0 / не приходит на frontend, но в БД данные есть.
/debug "баффы не показывают tier"
/debug проверяет 7 слоёв в порядке частоты багов:
| # | Слой | % багов | Что проверяет |
|---|---|---|---|
| 1 | Fastify Schema | 45% | Поле есть в response schema? |
| 2 | Controller | 20% | Поле передаётся в response? |
| 3 | Prisma Query | 15% | Поле в select/include? |
| 4 | Shared Types | 8% | Тип синхронизирован? |
| 5 | Service Return | 5% | Return включает поле? |
| 6 | Frontend | 4% | Правильный путь к данным? |
| 7 | Zod Validation | 3% | .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:
- Прогресс действительно не достигнут? → Проверить
currentvsrequired - Progress обновляется? → Искать
[Quest] Progress updatedдля этого userId - Webhook приходит? → Искать
path =~ "/webhook.*"с нужным eventType - Правильный serverId? → Проверить что сервер привязан к пользователю
"Награда не пришла"
Сигнатура в логах:
[BUSINESS] QUEST_REWARD_CLAIMED { userId, questId, rewardType, amount }
Если [BUSINESS] лог есть, но баланс не изменился:
- Проверить транзакцию → была ли ошибка после event?
- Проверить Prisma query →
$transactionзавершился?
Если [BUSINESS] лога нет:
- Искать
reasonв warn логах - Искать
errorв цепочке requestId
"Ошибка 500"
Сигнатура в логах:
[HTTP] Response { statusCode: 500, duration: 45, path: "/api/...", requestId: "..." }
Всегда:
- Скопировать
requestId - Найти все логи этого request — среди них будет
level: 50сerror.message - Типичные причины:
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:
- Webhook вообще приходит? → Искать
[HTTP] Incoming requestна/webhook - Валидация прошла? → Искать
[RustIntegration]логи - Идемпотентность → Повторный webhook? Проверить
RustWebhookLog
7. Инфраструктурные проблемы
Логи не появляются в Grafana
-
Приложение не выводит логи?
- Проверить
LOG_LEVELв Dokploy → Environment Variables LOG_LEVEL=infoне покажет debug логи
- Проверить
-
Promtail не работает?
docker logs promtail -
Loki не принимает?
curl -s http://localhost:3100/ready -
Docker socket?
docker exec promtail ls -la /var/run/docker.sock
Подробнее: Log Architecture → Troubleshooting
Нужно временно включить debug логи
- В Dokploy: изменить
LOG_LEVEL=debug - Перезапустить контейнер
- Воспроизвести проблему
- Не забыть вернуть
LOG_LEVEL=infoпосле диагностики
Логи старше 7 дней
Loki хранит логи 7 дней. Если проблема произошла раньше — логов уже нет.
Альтернативы:
- Docker logs на сервере (если не ротированы)
git logдля анализа что менялось в коде в тот период
8. Reference
Grafana Access
| Ресурс | URL | Доступ |
|---|---|---|
| Grafana UI | https://grafana.goloot.online | Логин/пароль (Traefik + SSL) |
| Grafana API | https://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
| Source | UID | Назначение | Когда использовать |
|---|---|---|---|
| Loki | loki | Логи | Поиск по userId, requestId, reason |
| Prometheus | prometheus | Метрики | Dashboards, rate of errors, latency, cache stats |
| Tempo | tempo | Traces | Waterfall запроса, bottleneck analysis |
Связанные документы
| Документ | Что покрывает |
|---|---|
| Observability | Архитектура: Logger API, Metrics, Tracing, Business Events |
| Log Architecture | Пайплайн: Pino → Docker → Promtail → Loki → Grafana |
| Logging Guidelines | Стандарты: формат, domain tags, reason codes |
| Monitoring README | Quick start мониторинг-стека |
Claude Skills для troubleshooting
| Skill | Когда использовать |
|---|---|
/debug "симптом" | Поле не приходит / null / 0 — проверка 7 слоёв |
/trace "проблема" | Нужны runtime-данные — добавление диагностических логов |
/investigate "вопрос" | Нужно понять архитектуру — deep research |
/db "запрос" | Проверить данные в БД напрямую |