Введение: почему логирование важно в TypeScript
Логирование — это не просто запись событий в файл. В современных TypeScript приложениях грамотно настроенное логирование является основой для отладки, мониторинга и анализа производительности. Без качественных логов разработчик тратит часы на воспроизведение ошибок, а команда эксплуатации не может быстро выявить узкие места.
TypeScript, как надстройка над JavaScript, предоставляет строгую типизацию, что позволяет создавать более предсказуемые и безопасные системы логирования. Однако использование простого console.log() в продакшене — это антипаттерн, который приводит к информационному шуму и потере важных данных.
В этой статье мы рассмотрим лучшие практики логирования в TypeScript: от выбора библиотеки до структурирования логов и работы с контекстом.
Выбор библиотеки для логирования
В экосистеме TypeScript/Node.js существует несколько популярных библиотек. Рассмотрим три основных варианта:
- Winston — наиболее популярная библиотека с гибкой настройкой транспортов (вывод в консоль, файл, базу данных). Поддерживает уровни логирования и форматирование.
- Pino — современная библиотека, ориентированная на производительность. Идеальна для высоконагруженных систем.
- Bunyan — менее популярный, но стабильный вариант. Все логгеры выводят данные в JSON-формате.
Для типового приложения на TypeScript мы рекомендуем Winston или Pino. Пример настройки Winston с TypeScript:
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'error.log', level: 'error' }),
],
});
logger.info('Сервер запущен', { port: 3000 });
Интеграция с TypeScript: типы и интерфейсы
Чтобы получить максимум от типизации, создайте обёртку для логгера с использованием интерфейсов:
interface LoggerOptions {
level?: string;
service?: string;
}
interface ILogger {
info(message: string, meta?: unknown): void;
error(message: string, error?: Error): void;
warn(message: string, meta?: unknown): void;
}
Это позволит вам строго контролировать типы передаваемых данных и избежать ошибок на этапе компиляции.
Уровни логирования и их правильное использование
Стандартные уровни логирования (от наиболее критичного к наименее): error, warn, info, debug, trace. Важно придерживаться единой семантики:
- error — только для невосстановимых ошибок, требующих внимания разработчика.
- warn — для потенциально проблемных ситуаций, которые не нарушают работу приложения.
- info — для ключевых событий: запуск сервера, обработка запроса, успешная авторизация.
- debug — для отладки в процессе разработки. Не включайте этот уровень в продакшене.
Пример динамического изменения уровня в зависимости от окружения:
const logLevel = process.env.NODE_ENV === 'production' ? 'warn' : 'debug';
const logger = winston.createLogger({ level: logLevel });
Структурированные логи и контекст
Вместо строковых сообщений используйте структурированные логи в формате JSON. Это позволяет легко парсить логи с помощью ELK-стека (Elasticsearch, Logstash, Kibana) или других систем.
Обязательно добавляйте контекстную информацию: ID запроса, имя пользователя, название сервиса, временную метку. Пример с использованием Pino:
import pino from 'pino';
const logger = pino({
level: 'info',
mixin() {
return { service: 'my-api' };
},
});
// Добавление контекста в конкретный лог
logger.info({ requestId: 'abc-123', userId: 42 }, 'Пользователь авторизован');
Для TypeScript также полезно создать middleware для Express или NestJS, который автоматически добавляет requestId в каждый лог:
import { Request, Response, NextFunction } from 'express';
import { v4 as uuidv4 } from 'uuid';
function requestLogger(req: Request, res: Response, next: NextFunction) {
req.requestId = uuidv4();
logger.info({ requestId: req.requestId, method: req.method, url: req.url }, 'Входящий запрос');
next();
}
Логирование ошибок и стектрейсов
Одна из частых ошибок — запись ошибки без стектрейса. Всегда передавайте объект ошибки целиком, а не только message:
try {
// опасный код
} catch (err) {
// Правильно:
logger.error('Ошибка при обработке запроса', err);
// Неправильно:
logger.error(`Ошибка: ${err.message}`);
}
Используйте встроенную поддержку ошибок в Winston: библиотека автоматически сериализует Error-объекты, сохраняя стек.
Производительность и ротация логов
Для продакшена критично настроить ротацию логов, чтобы файлы не занимали всё дисковое пространство. Winston поддерживает транспорты с ротацией через пакет winston-daily-rotate-file:
import DailyRotateFile from 'winston-daily-rotate-file';
const transport = new DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxFiles: '14d', // хранить логи 14 дней
});
Также не забывайте про асинхронность: в Pino логирование по умолчанию асинхронное, что минимизирует задержки в обработке запросов.
Заключение
Логирование в TypeScript — это не просто техническая необходимость, а стратегический инструмент для поддержания качества кода и стабильности приложения. Мы рассмотрели ключевые принципы:
- Выбор подходящей библиотеки (Winston для гибкости, Pino для скорости).
- Использование уровней логирования для фильтрации шума.
- Структурированные логи с контекстом для анализа.
- Корректная обработка ошибок с сохранением стектрейсов.
- Настройка ротации и производительности.
Начните с малого — замените console.log на структу