Logging em aplicações TypeScript: melhores práticas na prática
Logging é uma parte fundamental de qualquer aplicação em produção. Ele permite não apenas rastrear a execução do código e encontrar erros, mas também realizar auditoria de segurança, analisar desempenho e depurar cenários complexos. Em TypeScript, com seu poderoso sistema de tipos, a abordagem de logging se torna ainda mais flexível e confiável. Neste artigo, vamos abordar as melhores práticas de logging com exemplos concretos.
1. Escolha da biblioteca e configuração do logger
A primeira coisa a fazer é escolher uma biblioteca adequada. Entre as soluções populares, destacam-se winston e pino. Ambas oferecem suporte nativo a TypeScript, mas têm prioridades diferentes: winston — flexibilidade e funcionalidades ricas, pino — máxima performance.
Veja um exemplo de configuração do winston com tipagem:
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() ), defaultMeta: { service: 'user-service' }, 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;Para pino, a configuração será mais concisa, mas igualmente eficaz:
import pino from 'pino';
const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard', }, },});
export default logger;Dica: Sempre externalize a configuração do nível de logging para variáveis de ambiente (LOG_LEVEL). Em produção, geralmente usa-se info ou warn, e em desenvolvimento, debug.
2. Logging estruturado e contexto
Um dos maiores erros é registrar strings simples. Em vez disso, use objetos estruturados. Eles permitem filtrar e agregar logs facilmente em sistemas como ELK, Grafana ou Datadog.
Exemplo de logging ruim:
logger.info('User logged in successfully');Exemplo de logging bom com contexto:
logger.info({ message: 'User logged in', userId: user.id, email: user.email, loginMethod: 'oauth', ip: request.ip, duration: Date.now() - startTime,});Para facilitar, crie um wrapper tipado:
interface LogContext { requestId?: string; userId?: string; action?: string; [key: string]: unknown;}
function logWithContext(message: string, context: LogContext = {}): void { logger.info({ message, ...context });}
// UsologWithContext('Order processed', { orderId: '12345', userId: 'user_678', total: 99.99 });Essa abordagem garante que cada log contenha informações essenciais para análise.
3. Logging de erros e exceções
O tratamento de erros é a parte mais importante do logging. Sempre registre o stack trace completo do erro e o contexto em que ocorreu. Não use console.error — ele não oferece flexibilidade para rotação e formatação.
Padrão correto com TypeScript:
import logger from './logger';
async function fetchUserData(userId: string): Promise<UserData> { try { const response = await api.get(`/users/${userId}`); ret