FastifyMiddleTechnical

Как работает логирование запросов в Fastify? Как используется pino?

Fastify встраивает pino как логгер по умолчанию и автоматически логирует каждый запрос (метод, URL, statusCode, responseTime); внутри handlers используйте request.log для child-логгера с reqId, а в prod убирайте pino-pretty и пишите чистый JSON.

Логирование запросов в Fastify с pino

Fastify использует pino в качестве встроенного логгера. pino — один из самых быстрых JSON-логгеров для Node.js: он пишет в stdout синхронно через нативные методы, минимизируя влияние на latency. Fastify интегрирует pino на уровне фреймворка, автоматически логируя каждый входящий запрос и ответ.

Базовая настройка

import Fastify from 'fastify';

const fastify = Fastify({
  logger: {
    level: 'info', // trace | debug | info | warn | error | fatal
    // В dev — читаемый формат:
    transport: {
      target: 'pino-pretty',
      options: {
        colorize: true,
        translateTime: 'SYS:standard',
        ignore: 'pid,hostname'
      }
    }
  }
});

В продакшене transport убирают — pino пишет чистый JSON в stdout, который подхватывает Loki, Elasticsearch или любой другой агрегатор.

Что логируется автоматически

Без дополнительного кода Fastify логирует каждый запрос дважды:

  • incoming request — при получении запроса: метод, URL, hostname, remoteAddress, reqId
  • request completed — после отправки ответа: statusCode, responseTime (в мс)
// Пример JSON-строк в stdout:
// {"level":30,"reqId":"req-1","req":{"method":"GET","url":"/api/users","remoteAddress":"127.0.0.1"},"msg":"incoming request"}
// {"level":30,"reqId":"req-1","res":{"statusCode":200},"responseTime":12.34,"msg":"request completed"}

Использование логгера внутри handler

Каждый запрос имеет свой child-logger, связанный с reqId. Это позволяет фильтровать логи по конкретному запросу в агрегаторе.

fastify.get('/users/:id', async (request, reply) => {
  request.log.info({ userId: request.params.id }, 'fetching user');
  const user = await db.findUser(request.params.id);
  if (!user) {
    request.log.warn({ userId: request.params.id }, 'user not found');
    return reply.code(404).send({ error: 'Not found' });
  }
  request.log.debug({ user }, 'user fetched successfully');
  return user;
});

Настройка формата reqId и редактирование полей

import { randomUUID } from 'crypto';

const fastify = Fastify({
  logger: { level: 'info' },
  // Использовать UUID вместо инкрементального числа
  genReqId: (req) => req.headers['x-request-id'] ?? randomUUID(),
  // Сериализаторы — контроль над тем, что попадает в лог
  serializers: {
    req(request) {
      return {
        method: request.method,
        url: request.url,
        // НЕ логируем Authorization-заголовок:
        userAgent: request.headers['user-agent']
      };
    },
    res(reply) {
      return { statusCode: reply.statusCode };
    }
  }
});

Добавление correlation ID для distributed tracing

fastify.addHook('onRequest', async (request) => {
  // Пробрасываем trace-id от upstream (nginx, API Gateway)
  const traceId = request.headers['x-trace-id'] ?? request.id;
  // child-logger с дополнительными полями
  request.log = request.log.child({ traceId, env: process.env.NODE_ENV });
});

Логирование в production: pino + транспорт

// prod-logger.js — отдельный файл для конфигурации
const logger = {
  level: process.env.LOG_LEVEL ?? 'info',
  // pino-http совместимые поля для Loki/Grafana
  formatters: {
    level(label) { return { level: label }; } // строка вместо числа
  },
  timestamp: () => `,"timestamp":"${new Date().toISOString()}"`
};

export default logger;
// В docker-compose.yml — отправка логов в Loki:
// logging:
//   driver: loki
//   options:
//     loki-url: "http://loki:3100/loki/api/v1/push"
//     labels: "service=api"

Подводные камни

  • pino-pretty в production: этот транспорт добавляет overhead и не должен использоваться в prod; переключайтесь через переменную окружения NODE_ENV.
  • Логирование чувствительных данных: request.body может содержать пароли или токены — переопределите serializer для req, чтобы явно выбирать логируемые поля.
  • Отключение логирования снижает производительность неправильно: logger: false отключает логгер совсем, но если вы просто хотите убрать лишние поля — используйте serializers, а не отключение.
  • request.log vs fastify.log: fastify.log — глобальный логгер без reqId; request.log — child-logger с reqId. В хуках и handlers всегда используйте request.log для корреляции.
  • Уровень trace открывает много шума: уровень trace логирует внутренние события Fastify (парсинг маршрутов, вызовы hooks) — используйте только для отладки, не в prod.
  • Async transport в старых версиях pino: до pino v7 async transport требовал явной настройки; сейчас он включён по умолчанию, но важно проверить, что процесс не убивается до flush буфера (обработайте SIGTERM).

Common mistakes

  • Дает общий ответ про Node.js и не называет конкретные API Fastify.
  • Не объясняет, где в lifecycle находится логирование запросов через pino.
  • Не разделяет validation, authorization, business logic и persistence.
  • Игнорирует ошибки, лимиты входных данных, observability и тестирование.

What the interviewer is testing

  • Может объяснить логирование запросов через pino на примере кода.
  • Называет ключевые API: logger, request.log.
  • Использует точные API Fastify, а не вымышленные hooks/decorators/methods.
  • Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.

Sources

Related topics