Contexto y Rastreo de Peticiones
Contexto y Rastreo de Peticiones
Sección titulada «Contexto y Rastreo de Peticiones»Aprende cómo rastrear y gestionar peticiones en tus aplicaciones Veloce-TS con IDs de request automáticos, timeouts y soporte de cancelación.
Tabla de Contenidos
Sección titulada «Tabla de Contenidos»- Resumen
- Inicio Rápido
- Request ID
- AbortSignal
- Timeouts
- Integración con Logging
- Almacenamiento de Metadatos
- Mejores Prácticas
Resumen
Sección titulada «Resumen»Veloce-TS proporciona gestión mejorada del contexto de peticiones que te permite:
- Request IDs Automáticos - Cada petición recibe un UUID único
- Rastreo de Peticiones - Rastrea peticiones a través de logs y headers
- Soporte de Cancelación - Cancela operaciones largas con AbortSignal
- Gestión de Timeouts - Configura timeouts por ruta o globalmente
- Almacenamiento de Metadatos - Adjunta datos personalizados a peticiones
- Integración con Logging - El Request ID se propaga por todos los logs
Inicio Rápido
Sección titulada «Inicio Rápido»1. Habilitar Middleware de Request Context
Sección titulada «1. Habilitar Middleware de Request Context»import { VeloceTS, createRequestContextMiddleware } from 'veloce-ts';
const app = new VeloceTS();
// Habilitar request context con loggingapp.use(createRequestContextMiddleware({ timeout: 30000, // Timeout por defecto de 30 segundos logging: true, // Habilitar logging de peticiones logHeaders: ['user-agent', 'x-forwarded-for']}));2. Usar Request ID en Controllers
Sección titulada «2. Usar Request ID en Controllers»import { Controller, Get, RequestId } from 'veloce-ts';
@Controller('/users')class UserController { @Get('/:id') async getUser( @Param('id') id: string, @RequestId() requestId: string ) { logger.info({ requestId }, 'Obteniendo usuario'); return await db.users.findById(id); }}Request ID
Sección titulada «Request ID»Generación Automática
Sección titulada «Generación Automática»Cada petición recibe automáticamente un UUID único:
// Llega una petición// -> UUID generado: "abc-123-def-456"// -> Disponible en handlers vía @RequestId()// -> Enviado de vuelta en header X-Request-IDAcceder al Request ID
Sección titulada «Acceder al Request ID»En Controllers:
@Controller('/api')class ApiController { @Get('/data') async getData(@RequestId() requestId: string) { console.log('Request ID:', requestId); return { data: 'ejemplo', requestId }; }}En API Funcional:
import { getRequestId } from 'veloce-ts';
app.get('/data', { handler: async (c) => { const requestId = getRequestId(c); logger.info({ requestId }, 'Procesando petición'); return { data: 'ejemplo', requestId }; }});Desde Context:
import { getRequestId } from 'veloce-ts';
app.use(async (c, next) => { const requestId = getRequestId(c); console.log('Middleware - Request ID:', requestId); await next();});Headers de Respuesta
Sección titulada «Headers de Respuesta»El Request ID se incluye automáticamente en los headers de respuesta:
HTTP/1.1 200 OKX-Request-ID: abc-123-def-456Content-Type: application/jsonRequest ID Personalizado
Sección titulada «Request ID Personalizado»Proporciona tu propio request ID vía header:
curl -H "X-Request-ID: custom-id-123" http://localhost:3000/api/dataEl middleware usará el ID proporcionado si está disponible.
AbortSignal
Sección titulada «AbortSignal»Operaciones Cancelables
Sección titulada «Operaciones Cancelables»Usa AbortSignal para cancelar operaciones largas:
@Controller('/tasks')class TaskController { @Get('/process') async processTask(@AbortSignal() signal: AbortSignal) { // Pasar signal a operaciones largas return await heavyComputation({ signal }); }
@Get('/download') async downloadFile(@AbortSignal() signal: AbortSignal) { // Cancelar descarga si el cliente se desconecta return await streamLargeFile({ signal }); }}Con Fetch API
Sección titulada «Con Fetch API»@Get('/external')async fetchExternal(@AbortSignal() signal: AbortSignal) { try { const response = await fetch('https://api.example.com/data', { signal // Pasar signal a fetch }); return await response.json(); } catch (error) { if (error.name === 'AbortError') { return { error: 'La petición fue cancelada' }; } throw error; }}Verificación Manual de Cancelación
Sección titulada «Verificación Manual de Cancelación»@Get('/long-task')async longTask(@AbortSignal() signal: AbortSignal) { for (let i = 0; i < 100; i++) { // Verificar si fue cancelado if (signal.aborted) { return { cancelled: true, progress: i }; }
await processChunk(i); }
return { completed: true };}Escuchar Cancelación
Sección titulada «Escuchar Cancelación»@Get('/streaming')async streamData(@AbortSignal() signal: AbortSignal) { signal.addEventListener('abort', () => { console.log('Petición cancelada, limpiando...'); // Limpiar recursos });
return await streamLargeDataset();}Timeouts
Sección titulada «Timeouts»Timeout Global
Sección titulada «Timeout Global»Establecer timeout por defecto para todas las peticiones:
app.use(createRequestContextMiddleware({ timeout: 30000 // 30 segundos para todas las peticiones}));Timeout Específico por Ruta
Sección titulada «Timeout Específico por Ruta»Sobreescribir timeout para rutas específicas:
// API con Decoradores@Get('/slow-operation')@Timeout(60000) // 60 segundosasync slowOperation() { return await verySlowProcess();}
// API Funcionalapp.get('/slow', { timeout: 60000, handler: async () => { return await verySlowProcess(); }});Manejo de Timeouts
Sección titulada «Manejo de Timeouts»Cuando ocurre un timeout, la petición se aborta automáticamente:
@Get('/task')async task(@AbortSignal() signal: AbortSignal) { try { const result = await longRunningTask({ signal }); return { result }; } catch (error) { if (signal.aborted) { // Ocurrió timeout return { error: 'Timeout de petición' }; } throw error; }}Integración con Logging
Sección titulada «Integración con Logging»Request ID Automático en Logs
Sección titulada «Request ID Automático en Logs»Cuando usas el middleware de request context con logging habilitado, el request ID aparece automáticamente en todos los logs:
import { getLogger } from 'veloce-ts';
@Controller('/api')class ApiController { private logger = getLogger().child({ component: 'ApiController' });
@Get('/data') async getData(@RequestId() requestId: string) { // Todos estos logs incluirán el request ID this.logger.info('Iniciando obtención de datos'); this.logger.debug('Consultando base de datos'); this.logger.info('Obtención de datos completada');
return { data: 'ejemplo' }; }}Salida de log:
[2024-01-15 10:23:45] [requestId: abc-123] INFO: Iniciando obtención de datos[2024-01-15 10:23:45] [requestId: abc-123] DEBUG: Consultando base de datos[2024-01-15 10:23:45] [requestId: abc-123] INFO: Obtención de datos completadaChild Logger con Request Context
Sección titulada «Child Logger con Request Context»@Get('/users/:id')async getUser( @Param('id') id: string, @RequestId() requestId: string) { const logger = getLogger().child({ requestId, userId: id, component: 'UserController' });
logger.info('Obteniendo usuario');
try { const user = await db.users.findById(id); logger.info('Usuario obtenido exitosamente'); return user; } catch (error) { logger.error('Fallo al obtener usuario', error); throw error; }}Almacenamiento de Metadatos
Sección titulada «Almacenamiento de Metadatos»Adjuntar Datos Personalizados
Sección titulada «Adjuntar Datos Personalizados»Almacena datos personalizados en el contexto de la petición:
import { setRequestMetadata, getRequestMetadata } from 'veloce-ts';
// En middlewareapp.use(async (c, next) => { setRequestMetadata(c, 'startTime', Date.now()); await next();
const duration = Date.now() - getRequestMetadata(c, 'startTime'); console.log(`La petición tomó ${duration}ms`);});
// En handler@Get('/data')async getData(@Ctx() ctx: Context) { setRequestMetadata(ctx, 'cacheHit', true); return { data: 'ejemplo' };}Mejores Prácticas
Sección titulada «Mejores Prácticas»1. Siempre Usa Request Context Middleware
Sección titulada «1. Siempre Usa Request Context Middleware»Habilítalo temprano en tu cadena de middleware:
const app = new VeloceTS();
// Primer middlewareapp.use(createRequestContextMiddleware({ logging: true }));
// Luego otro middlewareapp.useCors();app.useRateLimit({ max: 100 });2. Incluye Request ID en Respuestas de Error
Sección titulada «2. Incluye Request ID en Respuestas de Error»app.onError((error, c) => { const requestId = getRequestId(c);
return c.json({ error: error.message, requestId, timestamp: new Date().toISOString() }, error.status || 500);});3. Pasa AbortSignal a Todas las Operaciones Async
Sección titulada «3. Pasa AbortSignal a Todas las Operaciones Async»@Get('/data')async getData(@AbortSignal() signal: AbortSignal) { // Pasar a queries de base de datos const users = await db.users.findMany({ signal });
// Pasar a APIs externas const external = await fetch('https://api.example.com', { signal });
// Pasar a funciones personalizadas const processed = await processData(users, { signal });
return processed;}Ejemplo Completo
Sección titulada «Ejemplo Completo»import { VeloceTS, Controller, Get, Post, Param, Body, RequestId, AbortSignal, Ctx, createRequestContextMiddleware, getLogger} from 'veloce-ts';
const app = new VeloceTS();
// Habilitar request contextapp.use(createRequestContextMiddleware({ timeout: 30000, logging: true, logHeaders: ['user-agent']}));
@Controller('/api')class ApiController { private logger = getLogger().child({ component: 'ApiController' });
@Get('/users/:id') async getUser( @Param('id') id: string, @RequestId() requestId: string, @AbortSignal() signal: AbortSignal ) { this.logger.info({ requestId, userId: id }, 'Obteniendo usuario');
try { if (signal.aborted) { return { error: 'Petición cancelada' }; }
const user = await db.users.findById(id, { signal }); this.logger.info({ requestId, userId: id }, 'Usuario obtenido');
return { user, requestId }; } catch (error) { this.logger.error({ requestId, userId: id, error }, 'Fallo al obtener usuario'); throw error; } }}
app.include(ApiController);app.listen(3000);Próximos Pasos
Sección titulada «Próximos Pasos»- Aprende sobre Caché para reducir tiempo de procesamiento
- Explora Logging para estrategias avanzadas de logging
- Revisa Manejo de Errores para gestión apropiada de errores