Logging Guidelines
Стандарты качества логирования и критерии "мусорных" логов.
Связанная документация
Для полного понимания observability-стека см. Observability. Этот документ фокусируется на качестве логов и критериях проверки.
1. Формат логов
Стандартный формат
logger.{level}('[Domain] Action description', { structuredData });
Правила
| Правило | Правильно | Неправильно |
|---|---|---|
[Domain] prefix | [Referral] Creating referral | Creating referral |
| Без emoji | [User] Level up | :tada: Level up! |
| Без ServiceName.method | [Quest] Claim rejected | QuestService.claim: |
| Данные в объекте | { userId, amount } | userId=${userId} |
| Английский | [Case] Opening case | [Case] Открытие кейса |
Domain Tags
| Домен в коде | Tag | Пример |
|---|---|---|
| achievements | [Achievement] | [Achievement] Progress updated |
| activation | [Activation] | [Activation] Session created |
| admin | [Admin] | [Admin] User banned |
| banners | [Banner] | [Banner] Impression recorded |
| cases | [Case] | [Case] Opening case |
| craft | [Craft] | [Craft] Item crafted |
| feedback | [Feedback] | [Feedback] Ticket created |
| history | [History] | [History] Entry added |
| inventory | [Inventory] | [Inventory] Item added |
| push | [Push] | [Push] Notification sent |
| quests | [Quest] | [Quest] Progress updated |
| quizzes | [Quiz] | [Quiz] Answer submitted |
| referrals | [Referral] | [Referral] Passive income claimed |
| seasons | [Season] | [Season] Rewards distributed |
| streaks | [Streak] | [Streak] Day claimed |
| telegram | [Telegram] | [Telegram] Webhook received |
| users | [User] | [User] XP added |
| utm | [UTM] | [UTM] Link tracked |
Примеры
// Правильно
logger.info('[Referral] Creating referral', { referrerId, referredId });
logger.error('[Referral] Failed to create referral', { error: err.message, referrerId });
logger.warn('[Referral] Rate limit approaching', { count: 95, limit: 100 });
// Неправильно
logger.info(':point_right: Creating referral:', { referrerId }); // emoji
logger.info('ReferralService.createReferral:', { referrerId }); // ServiceName
logger.info(`Creating referral for ${referrerId}`); // string interpolation
logger.info('[Referral] Создание реферала', { referrerId }); // русский
2. Критерии мусорных логов
УДАЛИТЬ (Trash Logs)
| # | Критерий | Пример | Почему мусор |
|---|---|---|---|
| 1 | Дублирует HTTP logs | [Controller] Getting quests | Fastify уже логирует [HTTP] Incoming request |
| 2 | "Started/Ended" без данных | Processing started | Не помогает при debugging |
| 3 | Step X паттерн | STEP_1: ..., STEP_2: ... | Слишком гранулярно, свернуть в 1-2 лога |
| 4 | Простой CRUD без контекста | Getting achievement by ID | Не бизнес-событие |
| 5 | console.* | console.log('debug') | Должен использовать logger |
| 6 | Emoji в сообщениях | logger.info(':rocket: Started') | Нарушает формат |
| 7 | ServiceName.method стиль | UserService.getById: | Устаревший формат |
| 8 | Интерполяция строк | User ${id} created | Данные должны быть в объекте |
ОСТАВИТЬ (Useful Logs)
| # | Критерий | Пример | Почему полезно |
|---|---|---|---|
| 1 | Бизнес-решение | Claim rejected: NOT_COMPLETED | Отвечает на "почему не засчиталось?" |
| 2 | Ошибка с контекстом | Failed to process: { error, userId } | Помогает debugging |
| 3 | logBusinessEvent | PASSIVE_INCOME_CLAIMED | Аудит экономики |
| 4 | Предупреждение | Rate limit approaching | Превентивный мониторинг |
| 5 | Критичная операция | Transaction completed | Audit trail |
Чеклист для каждого лога
Лог полезен если:
[ ] Помогает ответить на "почему не засчиталось?"
[ ] Содержит контекст для debugging (userId, entityId, reason)
[ ] НЕ дублирует HTTP middleware логи
[ ] НЕ является простым CRUD без бизнес-смысла
3. Уровни логирования
Когда использовать какой уровень
| Уровень | Когда | Пример |
|---|---|---|
debug | Детальная диагностика (выключен в prod) | Cache hit, validation passed |
info | Нормальное бизнес-событие | User leveled up, season started |
warn | Отказ по бизнес-правилу | Claim rejected: NOT_COMPLETED |
error | Ошибка (exception, но сервис работает) | Failed to send notification |
fatal | Критичная ошибка (сервис может упасть) | Database connection lost |
Типичные ошибки
// Неправильно: error для бизнес-отказа
logger.error('[Quest] Claim rejected: NOT_COMPLETED'); // Это НЕ ошибка!
// Правильно: warn для бизнес-отказа
logger.warn('[Quest] Claim rejected', { userId, questId, reason: 'NOT_COMPLETED' });
// Неправильно: info для exception
logger.info('[Quest] Failed to claim', { error: err.message }); // Это ошибка!
// Правильно: error для exception
logger.error('[Quest] Failed to claim', { error: err.message, userId });
4. Reason Codes
Стандартные коды для логирования отказов.
| Reason Code | Описание | Где используется |
|---|---|---|
NOT_COMPLETED | Прогресс не достигнут | Quests, achievements |
ALREADY_CLAIMED | Награда уже получена | Все claim endpoints |
COOLDOWN | Не прошёл cooldown | Spins, passive income |
NOT_FOUND | Сущность не найдена | Везде |
INSUFFICIENT_BALANCE | Недостаточно средств | Cases, craft, raffle |
INVALID_STATE | Неверное состояние | Withdrawals, quests |
SEASON_MISMATCH | Разные сезоны | Referrals, seasons |
LIMIT_REACHED | Достигнут лимит | Quests, promo codes |
SELF_REFERRAL | Попытка self-referral | Referrals |
EXPIRED | Истёк срок действия | Promo codes, sessions |
Паттерн использования
if (userQuest.status !== 'COMPLETED') {
logger.warn('[Quest] Claim rejected', {
userId,
questId,
reason: 'NOT_COMPLETED',
current: userQuest.progress,
required: quest.targetValue,
});
throw new BadRequestError('QUEST_NOT_COMPLETED');
}
5. Антипаттерны
Step X логи
// Неправильно: 24 лога
logger.info('RESET_STEP_1: Отменяем запросы', { userId });
logger.info('RESET_STEP_1_DONE: Запросы отменены', { count: 5 });
logger.info('RESET_STEP_2: Удаляем инвентарь', { userId });
// ... ещё 20 логов
// Правильно: 2 лога
logger.info('[User] Full reset started', { userId });
// ... операции без промежуточных логов
logger.info('[User] Full reset completed', {
userId,
stats: { withdrawals: 5, inventory: 12, caseOpenings: 8 },
duration: 1250
});
Controller boundary логи
// Неправильно: дублирует HTTP middleware
logger.info('[Controller] Getting quests', { userId });
const quests = await service.getQuests(userId);
logger.info('[Controller] Quests retrieved', { count: quests.length });
// Правильно: удалить оба лога
// HTTP логи уже пишутся автоматически через request-logger.hook.ts
const quests = await service.getQuests(userId);
console.log в доменном коде
// Неправильно
console.log('Debug:', { data });
console.error('Error:', err);
// Правильно
logger.debug('[Domain] Debug info', { data });
logger.error('[Domain] Operation failed', { error: err.message });
6. Исключения
Когда console.* допустим
| Место | Причина |
|---|---|
tracing.ts | Инициализация до logger |
index.ts (bootstrap) | Критичные ошибки запуска |
| Скрипты миграции | Одноразовые утилиты |
Когда debug логи допустимы в production коде
// Допустимо: сложная логика, которую трудно отлаживать
logger.debug('[Referral] Calculating passive income', {
referralsCount,
seasonMultiplier,
baseRate,
});
// Не допустимо: очевидные операции
logger.debug('[User] Getting user by ID'); // Убрать
7. Проверка качества логов
Команда /lint-logs
/lint-logs # Проверить файлы сессии
/lint-logs referrals # Проверить весь домен
/lint-logs --all # Проверить весь backend
Автоматическая проверка в /review
При выполнении /review автоматически проверяются:
console.*в доменном коде → Warning- Логи без
[Domain]prefix → Warning - Emoji в логах → Warning
Ручная проверка (bash)
# Найти console.* в доменах
grep -rE "console\.(log|warn|error)" backend/src/domains --include="*.ts" | grep -v ".test.ts"
# Найти логи без [Domain] формата
grep -rE "logger\.(info|error|warn)" backend/src/domains --include="*.ts" | grep -v ".test.ts" | grep -vE "'\["
# Найти emoji в логах
grep -rE "logger\.(info|error|warn|debug)" backend/src/domains --include="*.ts" | grep -E "[🎯✅❌⚠️🔄]"
Связанные документы
- Troubleshooting Guide — operational runbook: от жалобы пользователя до root cause
- Observability — полная документация observability стека
- Domain Structure — стандарт структуры домена