Una librería de logging versátil que funciona tanto con **Fastify** como de forma **standalone** (sin frameworks). Registra automáticamente todas las peticiones HTTP de manera estructurada, con soporte opcional para Google Cloud Logging.
npm install fastify-request-logsUna librería de logging versátil que funciona tanto con Fastify como de forma standalone (sin frameworks). Registra automáticamente todas las peticiones HTTP de manera estructurada, con soporte opcional para Google Cloud Logging.
- 🚀 Dual Mode: Funciona con Fastify y también de forma independiente
- 📊 Logs estructurados: JSON automático con toda la información de la petición
- 🌐 Google Cloud Logging: Integración nativa opcional
- 🔄 AsyncLocalStorage: Logging sin parámetros desde cualquier función
- 🎯 PubSub Ready: Perfecto para colas de mensajes, scripts, trabajos por lotes
- 🔧 TypeScript: Soporte completo con tipos
- ⚡ Alta performance: Optimizado para aplicaciones de alto tráfico
``bash`
npm install fastify-request-logs
- Uso con Fastify
- Uso Standalone (sin frameworks)
- Google Cloud Logging
- AsyncLocalStorage: Logging sin parámetros
- API Reference
- Configuración
`typescript
import fastify from 'fastify';
import { logger } from 'fastify-request-logs';
const app = fastify();
// Implementar el logger directamente
logger(app, {
only_errors: false,
domain: 'mi-api',
service: 'usuarios',
colors: true
});
app.get('/test', async (request, reply) => {
// El logger está disponible automáticamente en cada request
request.logger.add('custom-log', 'Este es un log personalizado');
return { message: 'Hello World' };
});
// Tipado recomendado en TypeScript
// import type { FastifyRequestWithLogger } from 'fastify-request-logs';
// app.get('/typed', async (request: FastifyRequestWithLogger) => {
// request.logger.add('typed-example', true);
// return { ok: true };
// });
`
Nuevo en v2.0.0: Ahora puedes usar la librería sin Fastify para casos como:
- Procesamiento de mensajes de colas (PubSub, SQS, etc.)
- Scripts de procesamiento por lotes
- Trabajos cron
- Cualquier aplicación que no sea un servidor HTTP
> El constructor de LoggerInstance solo necesita saber url, method, body y params. El campo logger es opcional, así que no tienes que inyectar una instancia manualmente.
`typescript
import {
LoggerInstance,
LoggerOptions,
printError,
printLog,
runWithLoggerAsync
} from 'fastify-request-logs';
type PubSubMessage = {
id: string;
data: Buffer;
ack: () => void;
nack: () => void;
};
type Etiqueta = {
id: number;
nombre: string;
};
const loggerOptions: LoggerOptions = {
only_errors: false,
domain: 'procesador-mensajes',
service: 'etiquetas',
module: 'carga-inicial',
colors: true
};
export const procesarMensaje = async (
mensaje: PubSubMessage
): Promise
// 1. Crear el logger standalone usando un payload que parece un request
const logger = new LoggerInstance(
{
url: /pubsub/carga-inicial/${mensaje.id},
method: 'MENSAJE',
body: mensaje.data.toString(),
params: { idMensaje: mensaje.id }
},
loggerOptions
);
try {
// 2. Ejecutar la lógica dentro del contexto AsyncLocalStorage
await runWithLoggerAsync(logger, async () => {
printLog('mensaje-recibido', mensaje.id);
const datos = JSON.parse(mensaje.data.toString()) as Etiqueta[];
printLog('datos-parseados', datos.length);
const servicio = new MiServicio();
await servicio.procesar(datos);
printLog('procesamiento-exitoso', true);
});
// 3. Confirmar el mensaje y cerrar el log
mensaje.ack();
logger.finish({ exitoso: true }, false, 200);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError('error-procesamiento', message);
if (esErrorRecuperable(error)) {
mensaje.nack();
} else {
mensaje.ack();
}
logger.finish({ exitoso: false, error: message }, true, 500);
}
};
class MiServicio {
public async procesar(etiquetas: Etiqueta[]): Promise
printLog('servicio-iniciado', etiquetas.length);
for (const etiqueta of etiquetas) {
printLog('procesando-etiqueta', etiqueta.id);
await this.validar(etiqueta);
await this.guardar(etiqueta);
printLog('etiqueta-guardada', etiqueta.id);
}
printLog('servicio-completado', true);
}
private async validar(etiqueta: Etiqueta): Promise
if (!etiqueta.nombre) {
throw new Error('Etiqueta sin nombre');
}
}
// Simula un guardado con un pequeño delay
private async guardar(etiqueta: Etiqueta): Promise
await new Promise((resolve) => setTimeout(resolve, 100));
if (etiqueta.id === 999) {
throw new Error('Error de base de datos');
}
}
}
const esErrorRecuperable = (error: unknown): boolean => {
const message = error instanceof Error ? error.message : String(error);
const normalized = message.toLowerCase();
if (
normalized.includes('json') ||
normalized.includes('invalid') ||
normalized.includes('parsing')
) {
return false;
}
return true;
};
`
#### 1. Procesamiento de mensajes de cola (PubSub, SQS, etc.)
`typescript
import type { Message } from '@google-cloud/pubsub';
import {
LoggerInstance,
LoggerOptions,
printError,
printLog,
runWithLoggerAsync
} from 'fastify-request-logs';
const baseOptions: LoggerOptions = {
only_errors: false,
domain: 'procesador-mensajes',
service: 'etiquetas',
module: 'carga-inicial',
colors: true
};
export const cargaInicialListener = async (mensaje: Message): Promise
const logger = new LoggerInstance(
{
url: /pubsub/carga-inicial/${mensaje.id},
method: 'MENSAJE',
body: mensaje.data?.toString(),
params: { idMensaje: mensaje.id }
},
{
...baseOptions,
useGCloudLogging: true,
gcloudProjectId: process.env.GOOGLE_CLOUD_PROJECT_ID
}
);
try {
await runWithLoggerAsync(logger, async () => {
printLog('mensaje-recibido', mensaje.id);
const validatedData = validateData
guardarEtiquetaSchema,
JSON.parse(mensaje.data.toString())
);
const service = DEPENDENCY_CONTAINER.get(GuardarEtiquetaService);
await service.guardarEtiqueta(validatedData);
printLog('procesamiento-exitoso', true);
});
mensaje.ack();
logger.finish({ exitoso: true }, false, 200);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError('error-procesamiento', message);
if (message.includes('validation') || message.includes('schema')) {
mensaje.ack();
} else {
mensaje.nack();
}
logger.finish({ exitoso: false, error: message }, true, 500);
}
};
`
#### 2. Scripts de procesamiento por lotes
`typescript
import { promises as fs } from 'node:fs';
import {
LoggerInstance,
LoggerOptions,
printLog,
runWithLoggerAsync
} from 'fastify-request-logs';
const batchLoggerOptions: LoggerOptions = {
only_errors: false,
domain: 'batch-processor',
service: 'file-handler',
colors: true
};
export const procesarArchivos = async (archivos: string[]): Promise
for (const archivo of archivos) {
const logger = new LoggerInstance(
{
url: /batch/archivo/${archivo},
method: 'BATCH_PROCESS',
body: { archivo },
params: { archivoId: archivo }
},
batchLoggerOptions
);
await runWithLoggerAsync(logger, async () => {
printLog('archivo-iniciado', archivo);
const contenido = await fs.readFile(archivo);
printLog('archivo-leido', contenido.length);
await procesarContenido(contenido); // Tu lógica de negocio aquí
printLog('archivo-completado', true);
logger.finish({ archivo, exitoso: true }, false, 200);
});
}
};
`
`typescript`
// Nuevas exportaciones disponibles
import {
LoggerInstance, // Clase principal del logger
printLog, // Agregar log sin parámetros
printError, // Agregar error sin parámetros
hasRequestLogger, // Verificar si hay contexto
runWithLogger, // Ejecutar en contexto (sync)
runWithLoggerAsync, // Ejecutar en contexto (async)
RequestContext // Acceso directo al contexto
} from 'fastify-request-logs';
`typescript`
interface LoggerOptions {
only_errors: boolean; // Solo registrar errores
domain: string; // Dominio de la aplicación
service: string; // Nombre del servicio
module?: string; // Módulo específico (opcional)
colors: boolean; // Habilitar colores en consola
useGCloudLogging?: boolean; // Usar Google Cloud Logging
gcloudProjectId?: string; // ID del proyecto de Google Cloud
}
Para usar Google Cloud Logging, configura las opciones así:
`typescript`
logger(app, {
only_errors: false,
domain: 'mi-api',
service: 'usuarios',
colors: true,
useGCloudLogging: true,
gcloudProjectId: 'mi-proyecto-gcp'
});
1. Instalar las dependencias de Google Cloud:
`bash`
npm install @google-cloud/logging
2. Configurar las credenciales de Google Cloud:
Opción A: Variable de entorno (Recomendado para producción)
`bash`
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/service-account-key.json"
Opción B: SDK de Google Cloud (Desarrollo local)
`bash`
gcloud auth application-default login
Opción C: Entornos de Google Cloud (Automático)
- Google Kubernetes Engine (GKE)
- Cloud Run
- Compute Engine
- App Engine
3. Configurar el proyecto de Google Cloud:
`bash`
gcloud config set project YOUR_PROJECT_ID
Para verificar que todo está configurado correctamente:
`bashVerificar credenciales
gcloud auth application-default print-access-token
$3
Cuando
useGCloudLogging está habilitado:- Logs estructurados: Los logs se envían con metadatos estructurados
- Severidad automática: Se asigna automáticamente
INFO o ERROR según el tipo de respuesta
- Etiquetas: Se incluyen automáticamente las etiquetas de domain, service y module
- HTTP Request metadata: Se incluye información de la petición HTTP (URL, método, status code)
- Fallback seguro: Si hay errores con Google Cloud Logging, automáticamente regresa a console.log$3
#### ❌ Error: "gcloudProjectId is required"
`typescript
// ❌ Incorrecto
logger(app, {
useGCloudLogging: true // Falta gcloudProjectId
});// ✅ Correcto
logger(app, {
useGCloudLogging: true,
gcloudProjectId: 'mi-proyecto-gcp' // Requerido
});
`#### ❌ Error: "Error initializing Google Cloud Logging"
Causa común: Credenciales no configuradas
Solución:
`bash
Para desarrollo local
gcloud auth application-default loginPara producción
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
`#### ❌ Error: "Failed to write to Google Cloud Logging"
Causas posibles:
1. Permisos insuficientes: El service account necesita el rol
roles/logging.logWriter
2. Proyecto incorrecto: Verificar que el gcloudProjectId sea correcto
3. API no habilitada: Habilitar Cloud Logging APISoluciones:
`bash
Habilitar la API de Cloud Logging
gcloud services enable logging.googleapis.comVerificar permisos del service account
gcloud projects get-iam-policy YOUR_PROJECT_IDOtorgar permisos de logging
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:your-service-account@your-project.iam.gserviceaccount.com" \
--role="roles/logging.logWriter"
`#### ⚠️ Warning: "Google Cloud Logging is enabled but not properly initialized"
Esto indica que
useGCloudLogging: true pero la inicialización falló. Revisa los mensajes de error anteriores para identificar la causa.#### 🔍 Verificar que los logs lleguen a Google Cloud
1. Ir a Google Cloud Console → Logging → Logs Explorer
2. Filtrar por:
`
resource.type="global"
logName="projects/YOUR_PROJECT_ID/logs/DOMAIN-SERVICE-logs"
`
3. Buscar logs recientes de tu aplicación$3
`json
{
"severity": "INFO",
"httpRequest": {
"status": 200,
"requestUrl": "/api/users",
"requestMethod": "GET"
},
"labels": {
"domain": "mi-api",
"service": "usuarios",
"module": "default"
},
"jsonPayload": {
"INFO": {
"__url": "/api/users",
"__method": "GET",
"__params": {},
"__body": {},
"domain": "mi-api",
"service": "usuarios"
},
"LOGS": {
"custom-log": "Este es un log personalizado"
},
"RESPONSE": {
"message": "Hello World",
"statusCode": 200
}
}
}
`📚 API Reference
$3
#### Método tradicional (sigue funcionando)
`typescript
app.get('/ejemplo', async (request, reply) => {
// Agregar logs personalizados
request.logger.add('usuario-id', request.user?.id);
request.logger.add('accion', 'consulta-datos');
// Registrar errores específicos
try {
// ... código que puede fallar
} catch (error) {
request.logger.error('database-error', error.message, 'DB_001');
}
return { success: true };
});
`#### Nuevo método con AsyncLocalStorage (✨ Recomendado)
`typescript
import { printLog, printError, getRequestLogger } from 'fastify-request-logs';// Funciones auxiliares que pueden hacer logging sin recibir parámetros
async function getUserData(userId) {
printLog('function-called', 'getUserData');
printLog('user-id', userId);
if (!userId) {
printError('validation-error', 'User ID is required', 'USER_001');
throw new Error('User ID is required');
}
return { id: userId, name: 'John Doe' };
}
app.get('/ejemplo', async (request, reply) => {
// ✅ Ahora puedes hacer logging desde cualquier función
printLog('route-accessed', '/ejemplo');
try {
const userData = await getUserData(request.params.id);
printLog('request-success', true);
return userData;
} catch (error) {
// Los errores ya se registraron automáticamente
reply.code(400);
return { error: error.message, isError: true };
}
});
`$3
`typescript
import {
printLog,
printError,
getRequestLogger,
hasRequestLogger,
runWithLogger,
runWithLoggerAsync
} from 'fastify-request-logs';// ✅ Funciones que funcionan tanto en Fastify como standalone
printLog('key', 'value'); // Retorna boolean
printError('error-type', 'Error message'); // Retorna boolean
hasRequestLogger(); // Verifica si hay contexto
getRequestLogger(); // Obtiene logger actual
// ✅ Solo para uso standalone
runWithLogger(logger, () => {
// Función síncrona
printLog('dentro-contexto', 'funciona');
});
await runWithLoggerAsync(logger, async () => {
// Función asíncrona
printLog('dentro-contexto-async', 'funciona');
await algunaOperacion();
});
`$3
-
printLog() ahora retorna boolean (antes void)
- printError() ahora retorna boolean (antes void)
- Nuevas funciones: hasRequestLogger(), runWithLogger(), runWithLoggerAsync()
- Nueva exportación: RequestContext, LoggerInstance$3
La librería detecta automáticamente payloads de Google Cloud Pub/Sub y los decodifica:
`typescript
// El body se decodifica automáticamente si es un mensaje de Pub/Sub
app.post('/webhook', async (request, reply) => {
// request.logger capturará automáticamente el mensaje decodificado
return { received: true };
});
`Configuración de desarrollo vs producción
$3
`typescript
logger(app, {
only_errors: false,
domain: 'dev-api',
service: 'usuarios',
colors: true,
useGCloudLogging: false // Usar console.log en desarrollo
});
`$3
`typescript
logger(app, {
only_errors: false,
domain: 'prod-api',
service: 'usuarios',
colors: false,
useGCloudLogging: true,
gcloudProjectId: process.env.GOOGLE_CLOUD_PROJECT_ID
});
`AsyncLocalStorage: Logging sin parámetros
$3
✅ Sin pasar parámetros: Puedes hacer logging desde cualquier función sin pasar
request.logger
✅ Agrupación automática: Todos los logs de una petición se agrupan automáticamente
✅ Seguro para concurrencia: Compatible con múltiples réplicas y alta carga
✅ Compatibilidad hacia atrás: El método tradicional sigue funcionando
✅ Funciona con async/await: Mantiene el contexto a través de operaciones asíncronas $3
`typescript
// Antes (incómodo)
async function processData(data, logger) {
logger.add('processing-data', data);
const result = await database.query(data);
logger.add('query-result', result);
return result;
}// Después (elegante)
async function processData(data) {
printLog('processing-data', data);
const result = await database.query(data);
printLog('query-result', result);
return result;
}
`$3
- Node.js 12.17.0+ (para AsyncLocalStorage)
- Funciona en cualquier entorno: On-premise, Google Cloud, AWS, etc.
- Compatible con clustering: Funciona con múltiples workers
Estructura de logs
Cada log incluye las siguientes secciones:
- INFO: Información básica de la petición (URL, método, parámetros, body)
- LOGS: Logs personalizados agregados con
request.logger.add() o printLog()
- RESPONSE: Respuesta exitosa del endpoint
- ERROR_RESPONSE: Respuesta de error (si aplica)
- ERRORS: Errores específicos registrados con request.logger.error() o printError()`MIT