Как работает логирование запросов в 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-риски: безопасность, отказоустойчивость, логирование и тесты.