Skip to main content

Logging Guidelines

Стандарты качества логирования и критерии "мусорных" логов.

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

Для полного понимания observability-стека см. Observability. Этот документ фокусируется на качестве логов и критериях проверки.


1. Формат логов

Стандартный формат

logger.{level}('[Domain] Action description', { structuredData });

Правила

ПравилоПравильноНеправильно
[Domain] prefix[Referral] Creating referralCreating referral
Без emoji[User] Level up:tada: Level up!
Без ServiceName.method[Quest] Claim rejectedQuestService.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 questsFastify уже логирует [HTTP] Incoming request
2"Started/Ended" без данныхProcessing startedНе помогает при debugging
3Step X паттернSTEP_1: ..., STEP_2: ...Слишком гранулярно, свернуть в 1-2 лога
4Простой CRUD без контекстаGetting achievement by IDНе бизнес-событие
5console.*console.log('debug')Должен использовать logger
6Emoji в сообщенияхlogger.info(':rocket: Started')Нарушает формат
7ServiceName.method стильUserService.getById:Устаревший формат
8Интерполяция строкUser ${id} createdДанные должны быть в объекте

ОСТАВИТЬ (Useful Logs)

#КритерийПримерПочему полезно
1Бизнес-решениеClaim rejected: NOT_COMPLETEDОтвечает на "почему не засчиталось?"
2Ошибка с контекстомFailed to process: { error, userId }Помогает debugging
3logBusinessEventPASSIVE_INCOME_CLAIMEDАудит экономики
4ПредупреждениеRate limit approachingПревентивный мониторинг
5Критичная операцияTransaction completedAudit 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Не прошёл cooldownSpins, 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-referralReferrals
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 — стандарт структуры домена