npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fastify-request-logs

v2.0.1

Published

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.

Readme

fastify-request-logs

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.

✨ Características principales

  • 🚀 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

Instalación

npm install fastify-request-logs

📋 Tabla de contenidos

🌐 Uso con Fastify

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 };
// });

🔧 Uso Standalone (sin frameworks)

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

Uso básico standalone

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.

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<void> => {
  // 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<void> {
    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<void> {
    if (!etiqueta.nombre) {
      throw new Error('Etiqueta sin nombre');
    }
  }

  // Simula un guardado con un pequeño delay
  private async guardar(etiqueta: Etiqueta): Promise<void> {
    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;
};

Casos de uso standalone

1. Procesamiento de mensajes de cola (PubSub, SQS, etc.)

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<void> => {
  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<EtiquetaCargaInicialModel[]>(
        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

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<void> => {
  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);
    });
  }
};

API Standalone

// 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';

Configuración

Opciones disponibles

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
}

Google Cloud Logging

Configuración básica

Para usar Google Cloud Logging, configura las opciones así:

logger(app, {
  only_errors: false,
  domain: 'mi-api',
  service: 'usuarios',
  colors: true,
  useGCloudLogging: true,
  gcloudProjectId: 'mi-proyecto-gcp'
});

Requisitos previos

  1. Instalar las dependencias de Google Cloud:

    npm install @google-cloud/logging
  2. Configurar las credenciales de Google Cloud:

    Opción A: Variable de entorno (Recomendado para producción)

    export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/service-account-key.json"

    Opción B: SDK de Google Cloud (Desarrollo local)

    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:

    gcloud config set project YOUR_PROJECT_ID

Verificar configuración

Para verificar que todo está configurado correctamente:

# Verificar credenciales
gcloud auth application-default print-access-token

# Verificar proyecto activo
gcloud config get-value project

Características de Google Cloud Logging

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

Troubleshooting

❌ Error: "gcloudProjectId is required"

// ❌ 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:

# Para desarrollo local
gcloud auth application-default login

# Para 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 API

Soluciones:

# Habilitar la API de Cloud Logging
gcloud services enable logging.googleapis.com

# Verificar permisos del service account
gcloud projects get-iam-policy YOUR_PROJECT_ID

# Otorgar permisos de logging
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
  --member="serviceAccount:[email protected]" \
  --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 ConsoleLoggingLogs Explorer
  2. Filtrar por:
    resource.type="global"
    logName="projects/YOUR_PROJECT_ID/logs/DOMAIN-SERVICE-logs"
  3. Buscar logs recientes de tu aplicación

Ejemplo de log en Google Cloud

{
  "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

Logging personalizado

Método tradicional (sigue funcionando)

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)

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 };
  }
});

Funciones de conveniencia disponibles

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();
});

Cambios en v2.0.0

  • printLog() ahora retorna boolean (antes void)
  • printError() ahora retorna boolean (antes void)
  • Nuevas funciones: hasRequestLogger(), runWithLogger(), runWithLoggerAsync()
  • Nueva exportación: RequestContext, LoggerInstance

Soporte para Pub/Sub

La librería detecta automáticamente payloads de Google Cloud Pub/Sub y los decodifica:

// 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

Desarrollo

logger(app, {
  only_errors: false,
  domain: 'dev-api',
  service: 'usuarios',
  colors: true,
  useGCloudLogging: false  // Usar console.log en desarrollo
});

Producción

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

Ventajas

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

Cómo funciona

// 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;
}

Requisitos

  • 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()

Licencia

MIT