Registro en aplicaciones TypeScript: mejores prácticas en la práctica
El registro es una parte fundamental de cualquier aplicación en producción. Permite no solo rastrear la ejecución del código y encontrar errores, sino también realizar auditorías de seguridad, analizar el rendimiento y depurar escenarios complejos. En TypeScript, con su potente sistema de tipos, el enfoque de registro se vuelve aún más flexible y confiable. En este artículo, analizaremos las mejores prácticas de registro con ejemplos concretos.
1. Elección de la biblioteca y configuración del logger
Lo primero por empezar es elegir una biblioteca adecuada. Entre las soluciones populares destacan winston y pino. Ambas soportan TypeScript de forma nativa, pero tienen diferentes prioridades: winston — flexibilidad y funcionalidad rica, pino — máximo rendimiento.
Consideremos un ejemplo de configuración de winston con tipado:
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, la configuración será más concisa, pero igualmente efectiva:
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;Consejo: Siempre externalice la configuración del nivel de registro en variables de entorno (LOG_LEVEL). En producción normalmente se usa info o warn, y en desarrollo — debug.
2. Registro estructurado y contexto
Uno de los principales errores es registrar cadenas simples. En su lugar, utilice objetos estructurados. Estos permiten filtrar y agregar fácilmente los registros en sistemas como ELK, Grafana o Datadog.
Ejemplo de mal registro:
logger.info('User logged in successfully');Ejemplo de buen registro con contexto:
logger.info({ message: 'User logged in', userId: user.id, email: user.email, loginMethod: 'oauth', ip: request.ip, duration: Date.now() - startTime,});Para mayor comodidad, puede crear un envoltorio 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 });Este enfoque garantiza que cada registro contenga información clave para el análisis.
3. Registro de errores y excepciones
El manejo de errores es la parte más importante del registro. Siempre registre la traza completa del error y el contexto en el que ocurrió. No use console.error — no ofrece flexibilidad para rotación y formato.
Patrón correcto con TypeScript:
import logger from './logger';
async function fetchUserData(userId: string): Promise<UserData> { try { const response = await api.get(`/users/${userId}`); ret