
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
levelal 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.logen producción: bypassa los niveles, los redact y los transports. Toda la app debe usarLoggero el inyectado pornestjs-pino.- Logging síncrono en bucles calientes: emitir miles de logs por segundo bloquea el event loop. Usa
pinocon transport asíncrono ylevel: 'info'en producción. - Datos sensibles sin redact: tokens JWT, contraseñas, tarjetas. Configurar
redactglobal con paths comoreq.body.password,req.headers.authorization. - No correlacionar con tracing: un log sin
traceIdno 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=warnen 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
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.