Logging

Intermedio
Nest
Nest
Actualizado: 05/05/2026

Diagrama: tutorial-nest-logging

Uso del Logger integrado y niveles de severidad

NestJS incluye un sistema de logging integrado disponible de forma nativa en controladores, servicios y providers. La clase Logger se importa desde @nestjs/common y se utiliza instanciándola con un contexto, generalmente el nombre de la clase. Ese contexto aparece como etiqueta en cada mensaje y facilita localizar el origen de un log en una aplicación con cientos de providers.

import { Injectable, Logger } from '@nestjs/common';

@Injectable()
export class ProductService {
  private readonly logger = new Logger(ProductService.name);

  findAll() {
    this.logger.log('Obteniendo todos los productos');
    return [];
  }

  create(product: { id: string; name: string }) {
    this.logger.log(`Creando producto: ${product.name}`);
    try {
      this.logger.log(`Producto creado con id ${product.id}`);
      return product;
    } catch (error) {
      this.logger.error(`Error al crear producto: ${(error as Error).message}`);
      throw error;
    }
  }
}

En un controlador, el logger es útil para registrar la entrada y salida de endpoints con información mínima que no comprometa datos personales. Conviene evitar imprimir cargas completas de request, tokens o identificadores personales sin anonimizar.

import { Controller, Get, Post, Body, Logger } from '@nestjs/common';

@Controller('products')
export class ProductController {
  private readonly logger = new Logger(ProductController.name);

  constructor(private readonly productService: ProductService) {}

  @Get()
  findAll() {
    this.logger.log('GET /products solicitado');
    const products = this.productService.findAll();
    this.logger.log(`GET /products devuelve ${products.length} productos`);
    return products;
  }

  @Post()
  create(@Body() createProductDto: { id: string; name: string }) {
    this.logger.log('POST /products recibido');
    return this.productService.create(createProductDto);
  }
}

El sistema expone cinco niveles que cubren las necesidades habituales. Cada uno tiene un propósito concreto y conviene respetar la convención para que el SIEM corporativo filtre y escale correctamente.

1 - log. Información general del flujo de la aplicación.

this.logger.log('Usuario autenticado correctamente');

2 - error. Excepciones y fallos que requieren atención.

this.logger.error('Error de conexión a la base de datos', (error as Error).stack);

3 - warn. Avisos que no detienen la ejecución pero que merecen revisión.

this.logger.warn('Límite de intentos de login alcanzado');

4 - debug. Detalle útil durante el desarrollo y la resolución de incidencias.

this.logger.debug({ userId, filters }, 'Parámetros recibidos en la petición');

5 - verbose. Información muy detallada, normalmente desactivada en producción.

this.logger.verbose('Iniciando proceso de validación de datos');

Los niveles siguen la convención habitual del ecosistema JavaScript y mapean con las severidades de syslog. Un dashboard en Grafana o Kibana puede colorearlas sin transformación adicional si el pipeline añade el campo level al evento.

Configuración global y integración con pino

La activación de niveles se controla al crear la aplicación. En desarrollo suele habilitarse todo, mientras que en producción se eliminan debug y verbose para reducir volumen y coste de almacenamiento.

import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const levels = process.env.NODE_ENV === 'production'
    ? ['error', 'warn', 'log']
    : ['error', 'warn', 'log', 'debug', 'verbose'];

  const app = await NestFactory.create(AppModule, {
    logger: levels as any,
  });

  new Logger('Bootstrap').log('Aplicación iniciada');
  await app.listen(3000);
}

bootstrap();

Para entornos distribuidos, el logger integrado se sustituye por uno externo que emita JSON estructurado y se integre con plataformas de observabilidad. nestjs-pino es la opción más extendida por su rendimiento y por emitir trazas ya alineadas con OpenTelemetry.

npm install nestjs-pino pino-http pino-pretty
import { Module } from '@nestjs/common';
import { LoggerModule } from 'nestjs-pino';

@Module({
  imports: [
    LoggerModule.forRoot({
      pinoHttp: {
        level: process.env.LOG_LEVEL ?? 'info',
        redact: ['req.headers.authorization', 'req.body.password'],
        transport: process.env.NODE_ENV !== 'production'
          ? { target: 'pino-pretty' }
          : undefined,
      },
    }),
  ],
})
export class AppModule {}

La clave redact enmascara campos sensibles antes de serializar, lo que mitiga riesgos de fuga de credenciales en logs. El flujo completo arranca en el cliente HTTP, atraviesa el controlador y los servicios con su Logger, pasa por el transport de pino y se emite como JSON por stdout. Un agente de envío (Loki, Filebeat o el agente de OpenTelemetry) recoge ese flujo y lo deposita en la plataforma de observabilidad, donde Grafana o Kibana lo visualizan correlado con métricas y trazas.

Caso aplicado en una fintech europea

Una fintech del sector de pagos que opera bajo la PSD2 y la DORA en la Unión Europea necesita trazabilidad completa de cada transacción. Cada petición incluye cabeceras traceparent propagadas desde el gateway, y el logger de NestJS añade ese identificador a todos los eventos generados durante el procesamiento de esa transacción.

import { Injectable, Logger, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import type { Request } from 'express';

@Injectable({ scope: Scope.REQUEST })
export class PaymentService {
  private readonly logger: Logger;

  constructor(@Inject(REQUEST) private readonly req: Request) {
    const traceId = (req.headers['traceparent'] as string)?.split('-')[1] ?? 'sin-traza';
    this.logger = new Logger(`PaymentService[${traceId}]`);
  }

  async procesar(pago: { id: string; importe: number; divisa: string }) {
    this.logger.log(`Iniciando pago ${pago.id} por ${pago.importe} ${pago.divisa}`);
    try {
      this.logger.log(`Pago ${pago.id} confirmado`);
      return { ok: true };
    } catch (e) {
      this.logger.error(`Fallo en pago ${pago.id}: ${(e as Error).message}`);
      throw e;
    }
  }
}

El resultado son logs JSON con traceId, spanId y atributos de negocio que llegan a Elasticsearch y alimentan cuadros de mando de compliance. Cuando el equipo de customer success investiga una incidencia, pega el identificador de trazabilidad que recibe el cliente y reconstruye el recorrido completo entre API gateway, microservicios y pasarela bancaria en cuestión de minutos.

Patrón de logging estructurado para auditoría

Los proyectos sometidos a auditoría (banca, salud, ENS, ISO 27001) requieren que los logs incluyan el quién, qué, cuándo y dónde de cada operación crítica. Un decorador @Audited() combinado con un interceptor permite escribir esos eventos de forma declarativa sin contaminar la lógica de negocio:

export const Audited = (action: string) => SetMetadata('audit:action', action);

@Injectable()
export class AuditInterceptor implements NestInterceptor {
  constructor(
    private readonly logger: PinoLogger,
    private readonly reflector: Reflector,
  ) {}

  intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {
    const action = this.reflector.get<string>('audit:action', ctx.getHandler());
    if (!action) return next.handle();
    const req = ctx.switchToHttp().getRequest();
    const start = Date.now();
    return next.handle().pipe(
      tap(() => this.logger.info({
        type: 'audit',
        action,
        userId: req.user?.id,
        tenantId: req.user?.tenantId,
        ip: req.ip,
        userAgent: req.headers['user-agent'],
        durationMs: Date.now() - start,
      }, 'audit-event')),
    );
  }
}

@Post('contracts')
@Audited('contract.signed')
sign(@Body() dto: SignDto, @CurrentUser() user: User) { /* ... */ }

Los eventos audit-event se enrutan a un índice Elasticsearch con retención de 7-10 años (cumplimiento legal) separado de los logs operacionales.

Errores comunes a evitar

  • console.log en producción: bypassa los niveles, los redact y los transports. Toda la app debe usar Logger o el inyectado por nestjs-pino.
  • Logging síncrono en bucles calientes: emitir miles de logs por segundo bloquea el event loop. Usa pino con transport asíncrono y level: 'info' en producción.
  • Datos sensibles sin redact: tokens JWT, contraseñas, tarjetas. Configurar redact global con paths como req.body.password, req.headers.authorization.
  • No correlacionar con tracing: un log sin traceId no se asocia con una traza distribuida y obliga a buscar manualmente en cada microservicio.
  • Logging de debug en CI: ralentiza tests y oculta los relevantes. Setear LOG_LEVEL=warn en jest.

Integración con OpenTelemetry y plataformas APM

nestjs-pino se integra con OpenTelemetry de forma transparente: los traceId y spanId del request quedan disponibles via @opentelemetry/api. Plataformas como Datadog APM, New Relic, Dynatrace o Grafana Cloud enriquecen automáticamente los logs con métricas (latencia, error rate) y permiten saltar de un log a la traza con un click.

Configuración mínima:

import { trace, context } from '@opentelemetry/api';

LoggerModule.forRoot({
  pinoHttp: {
    customProps: () => {
      const span = trace.getSpan(context.active());
      const sc = span?.spanContext();
      return sc ? { traceId: sc.traceId, spanId: sc.spanId } : {};
    },
  },
});

Este snippet añade traceId y spanId a cada log sin requerir cambios en los servicios. Es la base para investigar incidentes en arquitecturas con 50+ microservicios.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Nest

Documentación oficial de Nest
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Nest es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Nest

Explora más contenido relacionado con Nest y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Utilizar el Logger de NestJS en servicios y controladores con contexto por clase.
  • Aplicar los niveles log, error, warn, debug y verbose para distinguir severidad.
  • Configurar el Logger globalmente y filtrar niveles según el entorno.
  • Integrar un logger estructurado externo como pino para observabilidad en producción.