Logging
El sistema de logging de Fox Framework proporciona una forma flexible y potente para registrar eventos, errores y métricas en tu aplicación. Con múltiples niveles, transportes y formatos personalizables, el sistema permite adaptarse a necesidades específicas, desde desarrollo hasta producción.
Características Principales
- Sistema de niveles: debug, info, warn, error, fatal
- Múltiples transportes: consola, archivo, base de datos, servicios remotos
- Rotación de logs: por tamaño o tiempo
- Formatos personalizables: texto, JSON, personalizado
- Contexto enriquecido: información automática de HTTP, usuario, etc.
- Estructurado y filtrable: metadatos y etiquetas para fácil búsqueda
- Integración con servicios: Sentry, LogDNA, ELK Stack, etc.
- Bajo impacto en rendimiento: operaciones asíncronas y buffering
Configuración Básica
Para empezar a utilizar el sistema de logging:
import { FoxFactory } from '@foxframework/core';
import { LoggerFactory } from '@foxframework/logging';
// Crear logger con configuración
const logger = LoggerFactory.create({
// Nivel mínimo a registrar
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
// Transportes donde enviar logs
transports: [
// Consola para desarrollo
{ type: 'console', colorize: true },
// Archivo para producción
{
type: 'file',
filename: 'logs/app.log',
maxSize: '10m',
maxFiles: 5
}
],
// Formato del log
format: 'json', // 'text', 'json', o función personalizada
// Metadatos por defecto para todos los logs
defaultMeta: {
service: 'my-api',
version: '1.0.0'
}
});
// Registrar en la aplicación
const server = FoxFactory.createServer({
// Otras configuraciones...
logger
});
// También puedes usarlo directamente
logger.info('Servidor iniciado', { port: 3000 });
logger.error('Error al conectar a la base de datos', {
error: err.message,
dbHost: process.env.DB_HOST
});Uso Básico
Niveles de Log
Fox Framework proporciona varios niveles de log para diferentes situaciones:
// Para información detallada de desarrollo (solo en desarrollo)
logger.debug('Procesando solicitud de usuario', { userId, action });
// Para información operacional normal (lo más común)
logger.info('Usuario creado correctamente', { id: user.id });
// Para condiciones potencialmente problemáticas
logger.warn('Intentos de login múltiples', {
email,
attempts: 5,
ip: '192.168.1.1'
});
// Para errores que permiten continuar la operación
logger.error('Error al procesar pago', {
orderId: order.id,
error: paymentError.message,
code: paymentError.code
});
// Para errores críticos que requieren atención inmediata
logger.fatal('Excepción no controlada', {
error: err.stack,
context: { route, method, body }
});En Controladores
@Controller('/users')
export class UserController {
@Post('/')
async createUser(ctx: HttpContext) {
try {
const userData = ctx.body;
// Log info de operación
ctx.logger.info('Creando nuevo usuario', {
email: userData.email
});
const user = await this.userService.create(userData);
// Log de éxito
ctx.logger.info('Usuario creado correctamente', {
id: user.id,
email: user.email,
role: user.role
});
return user;
} catch (err) {
// Log de error con contexto completo
ctx.logger.error('Error al crear usuario', {
userData: ctx.body,
error: {
message: err.message,
stack: err.stack,
code: err.code
}
});
// Re-lanzar para que lo maneje el middleware de errores
throw err;
}
}
}En Servicios
@Injectable()
export class PaymentService {
constructor(
private paymentGateway: PaymentGateway,
private logger: Logger // Inyección automática del logger
) {}
async processPayment(orderId: string, amount: number, paymentData: any) {
try {
this.logger.debug('Iniciando procesamiento de pago', {
orderId,
amount,
paymentMethod: paymentData.method
});
const result = await this.paymentGateway.charge({
amount,
currency: 'USD',
source: paymentData.token,
description: `Order #${orderId}`
});
this.logger.info('Pago procesado correctamente', {
orderId,
transactionId: result.id,
status: result.status
});
return result;
} catch (err) {
this.logger.error('Error procesando pago', {
orderId,
error: err.message,
errorCode: err.code,
paymentMethod: paymentData.method
});
// Según el error, podríamos lanzar una excepción personalizada
if (err.code === 'insufficient_funds') {
throw new PaymentError('Fondos insuficientes', 'INSUFFICIENT_FUNDS');
}
throw err;
}
}
}Configuración Avanzada
Múltiples Transportes
Puedes configurar múltiples destinos para tus logs según tus necesidades:
import { LoggerFactory } from '@foxframework/logging';
const logger = LoggerFactory.create({
transports: [
// Consola para desarrollo
{
type: 'console',
level: 'debug', // Nivel mínimo para este transporte
colorize: true,
format: 'text'
},
// Archivo para errores y advertencias
{
type: 'file',
level: 'warn',
filename: 'logs/errors.log',
maxSize: '20m',
maxFiles: 14, // 14 días
format: 'json'
},
// Archivo para todos los logs
{
type: 'file',
level: 'info',
filename: 'logs/combined.log',
maxSize: '50m',
maxFiles: 7,
format: 'json'
},
// Base de datos para análisis
{
type: 'database',
level: 'info',
connection: dbConnection,
table: 'app_logs',
fields: {
timestamp: 'created_at',
level: 'level',
message: 'message',
metadata: 'data'
}
},
// Servicio externo para monitoreo
{
type: 'http',
level: 'error',
url: process.env.LOG_SERVICE_URL,
headers: {
'Authorization': `Bearer ${process.env.LOG_SERVICE_TOKEN}`,
'Content-Type': 'application/json'
},
batchSize: 10, // Enviar logs en lotes
retryCount: 3,
retryDelay: 1000
}
]
});Formatos Personalizados
Puedes personalizar el formato de tus logs:
const logger = LoggerFactory.create({
// Otras configuraciones...
format: (info) => {
// info contiene: timestamp, level, message, metadata, etc.
const { timestamp, level, message, ...metadata } = info;
// Para consola, formato legible
if (info.transport === 'console') {
return `${timestamp} [${level.toUpperCase()}] ${message} ${
Object.keys(metadata).length ? JSON.stringify(metadata, null, 2) : ''
}`;
}
// Para archivo, formato JSON estructurado
return JSON.stringify({
'@timestamp': timestamp,
level,
message,
environment: process.env.NODE_ENV,
host: os.hostname(),
...metadata
});
}
});Rotación de Logs
Para prevenir que los logs ocupen demasiado espacio:
const logger = LoggerFactory.create({
transports: [
// Rotación por tamaño
{
type: 'file',
filename: 'logs/app.log',
maxSize: '10m', // Rotar cuando alcance 10MB
maxFiles: 5, // Mantener 5 archivos rotados (app.log.1, app.log.2, etc.)
compress: true // Comprimir archivos rotados
},
// Rotación diaria
{
type: 'file',
filename: 'logs/%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d' // Mantener logs de los últimos 14 días
}
]
});Logging de HTTP
Registrar automáticamente las peticiones HTTP:
import { httpLogMiddleware } from '@foxframework/logging';
// Configuración básica
server.use(httpLogMiddleware());
// Configuración avanzada
server.use(httpLogMiddleware({
// Nivel de log para peticiones exitosas
level: 'info',
// Nivel para errores
errorLevel: 'error',
// Formato del mensaje
format: (ctx) => {
return `${ctx.method} ${ctx.url} - ${ctx.status}`;
},
// Metadatos a incluir
meta: (ctx) => {
return {
ip: ctx.ip,
userAgent: ctx.headers['user-agent'],
userId: ctx.auth?.user?.id,
responseTime: ctx.responseTime,
requestId: ctx.id
};
},
// Opciones para el cuerpo de la petición
body: {
include: true, // Incluir body en el log
maxLength: 1024, // Limitar longitud
sanitize: ['password', 'token', 'secret'] // Campos sensibles a sanitizar
},
// Rutas a excluir
ignorePaths: [
'/health',
'/metrics',
'/favicon.ico',
/^\/public\//
]
}));Ejemplo de log HTTP generado:
{
"timestamp": "2025-07-20T15:42:10.384Z",
"level": "info",
"message": "GET /api/users - 200",
"ip": "192.168.1.1",
"userAgent": "Mozilla/5.0...",
"userId": "user_123",
"method": "GET",
"url": "/api/users",
"status": 200,
"responseTime": 45,
"requestId": "req_abcdef123456",
"query": { "page": "1", "limit": "20" },
"requestSize": 0,
"responseSize": 1243
}Contexto y Correlación
Request ID y Correlación
Para rastrear peticiones a través de microservicios:
import { correlationMiddleware } from '@foxframework/logging';
// Middleware para ID de correlación
server.use(correlationMiddleware({
// Nombre del header para ID de correlación
header: 'X-Correlation-ID',
// Generar ID si no existe
generateIfMissing: true,
// Función de generación personalizada
generator: () => `${Date.now()}-${Math.random().toString(36).substring(2, 10)}`
}));
// Ahora puedes acceder al ID en cualquier middleware o controlador
server.use((ctx, next) => {
const correlationId = ctx.correlationId;
// El logger incluirá automáticamente este ID
ctx.logger.info('Procesando solicitud', {
// No es necesario incluir correlationId, se añade automáticamente
action: 'process-request'
});
return next();
});
// El middleware de HTTP también lo incluirá en la respuesta
// Headers de respuesta: { 'X-Correlation-ID': '1627384950123-abc123def' }Contexto Enriquecido
Añadir contexto adicional a todos los logs de una solicitud:
// En un middleware
server.use((ctx, next) => {
// Si hay usuario autenticado, añadir al contexto de logs
if (ctx.auth?.user) {
ctx.logger = ctx.logger.child({
userId: ctx.auth.user.id,
userRole: ctx.auth.user.role
});
}
// Si hay información de dispositivo, añadirla
if (ctx.headers['user-agent']) {
const device = parseUserAgent(ctx.headers['user-agent']);
ctx.logger = ctx.logger.child({
device: {
type: device.type,
browser: device.browser,
os: device.os
}
});
}
return next();
});Servicios de Logging Externos
Integración con Sentry
Para registrar errores en Sentry:
import { LoggerFactory } from '@foxframework/logging';
import { SentryTransport } from '@foxframework/logging';
// Configurar logger con Sentry
const logger = LoggerFactory.create({
// Otras configuraciones...
transports: [
// Transportes regulares...
// Sentry para errores
new SentryTransport({
level: 'error',
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
release: process.env.APP_VERSION,
// Transformar logs a eventos de Sentry
transformEvent: (info) => {
// Extraer datos útiles del log
const { error, user, tags, ...extra } = info.metadata || {};
return {
message: info.message,
level: info.level,
user,
tags,
extra
};
}
})
]
});Integración con ELK Stack
Para enviar logs a Elasticsearch:
import { LoggerFactory } from '@foxframework/logging';
import { ElasticsearchTransport } from '@foxframework/logging';
const logger = LoggerFactory.create({
transports: [
// Otros transportes...
// Elasticsearch para todos los logs
new ElasticsearchTransport({
level: 'info',
clientOpts: {
node: process.env.ELASTICSEARCH_URL,
auth: {
username: process.env.ELASTICSEARCH_USERNAME,
password: process.env.ELASTICSEARCH_PASSWORD
}
},
indexPrefix: 'app-logs',
indexSuffixPattern: 'YYYY.MM.DD',
mappingTemplate: {
// Template de mapping para Elasticsearch
}
})
]
});Performance y Optimización
Logging Asíncrono
Para no bloquear el hilo principal:
const logger = LoggerFactory.create({
// Otras configuraciones...
// Modo asíncrono
async: true,
// Tamaño del buffer para operaciones asíncronas
bufferSize: 1000,
// Intervalo de procesamiento del buffer (ms)
flushInterval: 5000,
// Estrategia si el buffer se llena
bufferOverflowStrategy: 'drop' // 'drop' o 'flush'
});Filtrado y Muestreo
Para reducir el volumen de logs:
const logger = LoggerFactory.create({
// Otras configuraciones...
// Filtros
filters: [
// Ignorar logs de salud
(info) => !(info.message.includes('health check') && info.level === 'debug'),
// Ignorar ciertos errores
(info) => !(info.level === 'error' &&
info.metadata?.error?.code === 'ECONNRESET')
],
// Muestreo (registrar solo un % de los logs)
sampling: {
// Para logs de debug, solo registrar 10%
debug: 0.1,
// Para logs de info sobre peticiones HTTP, muestrear al 50%
custom: [
{
test: (info) =>
info.level === 'info' &&
info.metadata?.type === 'http-request',
rate: 0.5
}
]
}
});Logging en Entornos Específicos
Desarrollo
if (process.env.NODE_ENV === 'development') {
logger.configure({
level: 'debug',
transports: [
{
type: 'console',
colorize: true,
format: 'text',
prettyPrint: true
}
]
});
}Pruebas
if (process.env.NODE_ENV === 'test') {
logger.configure({
level: 'error', // Minimizar ruido durante tests
transports: [
{
type: 'memory', // Guardar logs en memoria para verificaciones
maxSize: 100
}
]
});
// Acceder a logs en memoria para assertions
afterEach(() => {
const logs = logger.getTransport('memory').getLogs();
expect(logs).toHaveLength(0); // No debería haber errores
});
}Producción
if (process.env.NODE_ENV === 'production') {
logger.configure({
level: 'info',
transports: [
// Error logs
{
type: 'file',
level: 'error',
filename: '/var/log/app/error.log',
maxSize: '50m',
maxFiles: 10,
format: 'json'
},
// Logs combinados
{
type: 'file',
level: 'info',
filename: '/var/log/app/combined.log',
maxSize: '100m',
maxFiles: 7,
format: 'json'
},
// Alertas críticas a Slack
{
type: 'webhook',
level: 'fatal',
url: process.env.SLACK_WEBHOOK_URL,
formatter: (info) => ({
text: `🚨 *ALERTA CRÍTICA*: ${info.message}`,
attachments: [
{
color: '#FF0000',
fields: [
{ title: 'Error', value: info.metadata.error?.message || 'Desconocido' },
{ title: 'Servidor', value: os.hostname() },
{ title: 'Timestamp', value: info.timestamp }
],
footer: 'Fox Framework Production'
}
]
})
}
]
});
}Integración con Monitor de Aplicaciones
import { LoggerFactory } from '@foxframework/logging';
import { AppMonitor } from '@foxframework/monitoring';
// Crear monitor de aplicación
const monitor = new AppMonitor({
// Configuración de monitoreo...
});
// Integrar logger con monitor
const logger = LoggerFactory.create({
// Otras configuraciones...
// Eventos para métricas
on: {
log: (info) => {
// Incrementar contador según nivel de log
monitor.increment(`logs.${info.level}`);
// Para errores, aumentar contador específico
if (info.level === 'error' || info.level === 'fatal') {
const errorCode = info.metadata?.error?.code || 'unknown';
monitor.increment(`errors.${errorCode}`);
}
}
}
});
// Middleware para métricas de errores
server.use(async (ctx, next) => {
try {
await next();
} catch (err) {
// Registrar error en logger
ctx.logger.error('Error no controlado', { error: err });
// Actualizar métricas
monitor.increment('errors.unhandled');
monitor.histogram('errors.response_time', ctx.responseTime);
throw err;
}
});Buenas Prácticas
Estructurar los Logs
Para facilitar búsqueda y análisis:
// ❌ NO: Log sin estructura
logger.info(`Usuario ${user.id} ha comprado ${cart.items.length} productos por $${order.total}`);
// ✅ SÍ: Log estructurado con metadatos
logger.info('Compra completada', {
userId: user.id,
items: cart.items.length,
total: order.total,
currency: 'USD',
paymentMethod: order.paymentMethod
});Niveles Adecuados
Usar el nivel correcto para cada tipo de mensaje:
// ❌ NO: Nivel incorrecto
logger.error('Usuario ha iniciado sesión'); // Evento normal como error
// ✅ SÍ: Nivel adecuado
logger.debug('Validando credenciales de usuario'); // Detalles de desarrollo
logger.info('Usuario ha iniciado sesión'); // Eventos normales
logger.warn('Múltiples intentos de login'); // Situaciones sospechosas
logger.error('Error en pasarela de pago'); // Errores recuperables
logger.fatal('Base de datos no disponible'); // Errores críticosInformación Sensible
Evitar registrar datos sensibles:
// ❌ NO: Datos sensibles en logs
logger.info('Usuario autenticado', {
user: {
id: 1,
email: 'usuario@ejemplo.com',
password: '123456', // NUNCA incluir contraseñas
creditCard: '4111-1111-1111-1111' // NUNCA incluir datos de tarjetas
}
});
// ✅ SÍ: Sanitizar datos sensibles
logger.info('Usuario autenticado', {
user: {
id: 1,
email: sanitizeEmail('usuario@ejemplo.com'), // Ej: 'u***@ejemplo.com'
hasPassword: true
}
});
// Otra opción: Usar sanitizador automático
const logger = LoggerFactory.create({
// Otras configuraciones...
sanitize: {
// Campos a sanitizar
fields: ['password', 'token', 'secret', 'credit_card', 'ssn'],
// Cómo sanitizar (sustituir, truncar, hash, etc.)
replacement: '[REDACTED]'
}
});Logging Contextual
Proporcionar contexto suficiente:
// ❌ NO: Mensaje sin contexto
logger.error('Error de base de datos');
// ✅ SÍ: Error con contexto completo
logger.error('Error de conexión a base de datos', {
database: config.database.name,
host: config.database.host,
error: {
code: err.code,
message: err.message,
query: sanitizeQuery(err.query) // Eliminar parámetros sensibles
},
retryAttempt: 2,
willRetry: retryCount < maxRetries
});Casos de Uso Avanzados
Logs para Auditoría
Registros específicos para auditoría de seguridad:
import { AuditLogger } from '@foxframework/logging';
// Crear logger específico para auditoría
const auditLogger = new AuditLogger({
// Transportes específicos para registros de auditoría
transports: [
// Archivo inmutable para auditoría
{
type: 'file',
filename: '/var/log/app/audit.log',
maxSize: '100m',
maxFiles: 365, // Retener 1 año
format: 'json',
// Opciones de seguridad para auditoría
secure: true,
permissions: 0o400 // Solo lectura para el propietario
},
// Base de datos para consultas
{
type: 'database',
connection: dbConnection,
table: 'audit_logs'
}
]
});
// Middleware para inyectar logger de auditoría
server.use((ctx, next) => {
ctx.audit = auditLogger;
return next();
});
// Ejemplo de uso en controlador
@Controller('/admin')
export class AdminController {
@Post('/users/:id/suspend')
@Role('admin')
async suspendUser(ctx: HttpContext) {
const { id } = ctx.params;
const { reason } = ctx.body;
await this.userService.suspend(id, reason);
// Registrar acción para auditoría
ctx.audit.log({
action: 'user.suspend',
actor: {
id: ctx.auth.user.id,
name: ctx.auth.user.name,
role: ctx.auth.user.role
},
target: {
id,
type: 'user'
},
context: {
reason,
ip: ctx.ip,
userAgent: ctx.headers['user-agent']
},
result: 'success'
});
return { success: true };
}
}Logging Distribuido
Para sistemas distribuidos y microservicios:
import { DistributedLogger } from '@foxframework/logging';
import { TraceContext } from '@foxframework/tracing';
// Crear logger distribuido
const logger = new DistributedLogger({
// Configuración base
serviceName: 'user-service',
serviceVersion: '1.2.0',
// Integración con sistema de trazas
tracing: {
enabled: true,
propagateContext: true
},
// Formato B3 para propagación de contexto
propagation: {
format: 'b3', // 'b3', 'w3c', 'jaeger'
headers: {
traceId: 'X-B3-TraceId',
spanId: 'X-B3-SpanId',
parentSpanId: 'X-B3-ParentSpanId',
sampled: 'X-B3-Sampled'
}
},
// Transportes
transports: [
// Local para desarrollo
{ type: 'console', format: 'text' },
// Centralizado para producción
{
type: 'fluentd',
host: process.env.FLUENTD_HOST,
port: parseInt(process.env.FLUENTD_PORT || '24224'),
timeout: 3000,
reconnectInterval: 5000
}
]
});
// Middleware para extraer/inyectar contexto de trazas
server.use((ctx, next) => {
// Extraer contexto de traza de los headers
const traceContext = TraceContext.fromRequest(ctx.headers);
// Crear logger con contexto de traza
ctx.logger = logger.withTraceContext(traceContext);
return next();
});
// Ejemplo de llamada a otro servicio con propagación
async function callUserService(ctx, userId) {
const traceHeaders = TraceContext.toHeaders(ctx.traceContext);
const response = await fetch(`${USER_SERVICE_URL}/users/${userId}`, {
headers: {
...traceHeaders,
'Content-Type': 'application/json'
}
});
return response.json();
}Logging como Eventos de Negocio
Para análisis de comportamiento y flujos de negocio:
import { BusinessEventLogger } from '@foxframework/logging';
// Crear logger para eventos de negocio
const eventLogger = new BusinessEventLogger({
// Transportes específicos para eventos
transports: [
// Kafka para procesamiento en tiempo real
{
type: 'kafka',
brokers: process.env.KAFKA_BROKERS?.split(',') || [],
topic: 'business-events',
compression: 'gzip'
},
// Base de datos para análisis histórico
{
type: 'database',
connection: analyticsDbConnection,
table: 'business_events'
}
]
});
// En un servicio de compras
class OrderService {
async createOrder(user, cart) {
// Lógica para crear orden
// Registrar evento de negocio
eventLogger.event('order.created', {
// Datos del evento
orderId: order.id,
userId: user.id,
amount: order.total,
currency: order.currency,
items: order.items.map(item => ({
productId: item.productId,
quantity: item.quantity,
price: item.price
})),
// Metadatos del evento
channel: user.acquisitionChannel,
userType: user.type,
isFirstPurchase: user.ordersCount === 0,
deviceType: user.deviceInfo?.type || 'unknown'
});
return order;
}
}Conclusión
El sistema de logging de Fox Framework proporciona una solución completa para gestionar logs en aplicaciones modernas, desde simples mensajes de consola hasta configuraciones avanzadas para sistemas distribuidos y análisis de datos. Con múltiples niveles, transportes configurables y capacidades de filtrado y enriquecimiento, el sistema se adapta a las necesidades de aplicaciones de cualquier tamaño, desde pequeñas APIs hasta sistemas empresariales complejos.
Al implementar buenas prácticas de logging como:
- Estructurar los datos para facilitar búsquedas y análisis
- Usar los niveles adecuados para cada tipo de mensaje
- Proporcionar contexto suficiente para depuración
- Sanitizar información sensible para cumplir con regulaciones
- Separar logs según su propósito (aplicación, auditoría, eventos)
Fox Framework te permite no solo depurar problemas de forma efectiva, sino también extraer valor de los logs para mejorar la calidad y rendimiento de tu aplicación.