Введение в логирование TypeScript приложений
Логирование — это процесс записи событий, происходящих во время работы программы. Для TypeScript-приложений, особенно работающих в среде Node.js, грамотно настроенное логирование является не просто хорошим тоном, а критически важным инструментом для отладки, мониторинга и анализа ошибок.
Многие начинающие разработчики ограничиваются использованием console.log(). Однако в production-среде такой подход неэффективен: он не позволяет гибко управлять уровнями важности, фильтровать сообщения или сохранять их в файлы. В этой статье мы разберём, как создать профессиональную систему логирования в TypeScript с нуля.
Почему не стоит использовать console.log в production?
Взглянем на типичный пример:
// Плохой подход — используем console.log напрямую
function processOrder(orderId: string): void {
console.log(`Processing order ${orderId}`);
try {
// бизнес-логика
} catch (error) {
console.error(`Error processing order: ${error}`);
}
}
Проблемы такого подхода:
- Отсутствие уровней логирования: нельзя отделить debug-сообщения от критических ошибок.
- Нет форматирования: сложно читать большие объёмы данных.
- Нельзя перенаправить: все сообщения идут только в stdout/stderr.
- Проблемы с производительностью: синхронная запись в консоль может замедлить приложение.
Лучшие практики логирования в TypeScript
1. Выбор библиотеки для логирования
Для TypeScript рекомендуется использовать одну из двух популярных библиотек:
- Winston — мощный и гибкий логгер с поддержкой транспортов (файл, консоль, базы данных).
- Pino — сверхбыстрый логгер, идеальный для high-load приложений.
Установим Winston:
npm install winston
npm install @types/winston --save-dev
2. Создание конфигурируемого логгера
Создадим модуль logger.ts:
import winston from 'winston';
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
})
]
});
export default logger;
Теперь можно использовать логгер в любом модуле:
import logger from './logger';
function getUser(id: string) {
logger.info(`Fetching user with ID: ${id}`);
try {
// логика получения пользователя
logger.debug('User data retrieved successfully');
} catch (error) {
logger.error('Failed to fetch user', { userId: id, error });
}
}
3. Использование уровней логирования
Стандартные уровни (от наимение важного к самому важному):
- debug — детальная отладочная информация (только для разработки).
- info — стандартные информационные сообщения.
- warn — предупреждения о потенциальных проблемах.
- error — ошибки, требующие внимания.
Настройте уровень логирования через переменные окружения:
// В .env файле
LOG_LEVEL=debug # для разработки
LOG_LEVEL=warn # для production
4. Структурированное логирование
Передавайте дополнительный контекст в виде объекта:
logger.info('Order processed', {
orderId: '12345',
userId: 'user_678',
amount: 150.50,
duration: 245 // ms
});
Это позволяет легко анализировать логи с помощью инструментов вроде ELK Stack или Grafana.
Продвинутые техники для TypeScript
Типизация логгера
Создайте типизированную обёртку:
interface LoggerContext {
requestId?: string;
userId?: string;
service?: string;
}
class TypedLogger {
private logger: winston.Logger;
constructor(context?: LoggerContext) {
this.logger = winston.createLogger({
defaultMeta: context || {}
});
}
info(message: string, meta?: Record<string, unknown>) {
this.logger.info(message, meta);
}
error(message: string, error?: Error, meta?: Record<string, unknown>) {
this.logger.error(message, { ...meta, error: error?.stack });
}
}
export default TypedLogger;
Асинхронное логирование
Для высоконагруженных систем используйте Pino с асинхронным транспортом:
import pino from 'pino';
const logger = pino({
transport: {
target: 'pino/file',
options: { destination: './logs/app.log', sync: false }
}
});
Заключение
Грамотное логирование в TypeScript — это не просто замена console.log на библиотеку. Это архитектурное решение, которое включает:
- Выбор подходящей библиотеки (Winston для гибкости, Pino для скорости).
- Настройку уровней логирования через переменные окружения.
- Структурированное логирование с контекстом.
- Разделение транспортов для разных уровней.
Начните с внедрения Winston в ваш проект — это даст 80% необходимого функционала. По мере роста приложения вы сможете добавить централизованный сбор логов, мониторинг и алерты. Помните: хорошие логи экономят часы отладки и помогают быстрее находить причины проблем в production.