Skip to content

Logging

Logging uses Pino with a request-scoped context. The LoggerInterceptor creates a requestId at the start of each HTTP request, stores it in AsyncLocalStorage, and the Pino logger mixin includes it in every log line emitted during that request.

  1. The interceptor starts a timer and reads the client IP.
  2. It generates a requestId (or accepts one from the x-request-id header).
  3. It runs the request inside an AsyncLocalStorage context.
  4. Pino’s mixin reads the store and adds requestId to the log line.
  5. On response, the interceptor logs the duration and status code.

This means a single request spans multiple log lines with the same ID, which is useful for tracing errors through guards, services, and repositories.

LoggerServiceBuilder creates a NestJS-compatible logger backed by Pino:

const logger = app.get(LoggerServiceBuilder).build();
app.useLogger(logger);

Inside any provider, use NestJS’s standard Logger or inject the same builder. The requestId is picked up automatically if the call is within the request context.

In development, pino-pretty prints readable logs:

[2025-...] INFO (12345): Request started from IP: ..., Path: /api/v1/authentication/login, Method: POST at ...
requestId: "abc-123"
[2025-...] INFO (12345): Response completed in 23ms 🔥 with Status:200
requestId: "abc-123"

In production, Pino outputs JSON:

{
"level": 30,
"time": 1234567890123,
"pid": 12345,
"hostname": "...",
"requestId": "abc-123",
"msg": "Response completed in 23ms 🔥 with Status:200"
}

The LoggerStore interface currently only stores requestId. You can extend it with userId, tenantId, or other fields and update the interceptor to populate them. Anywhere you inject ASYNC_STORAGE, you can read the current store with als.getStore().

The LogContext decorator wraps a method and auto-logs entry, duration, success, and errors. It is useful for background jobs or service methods where you want a consistent log envelope without manual log calls. The decorator expects the class to have a logger property.

Pino supports transports for file output, log aggregation, or cloud providers. You can add a transport by changing the transport option in LoggerServiceBuilder. For production, the default configuration leaves transport undefined so Pino writes JSON to stdout, which is the best default for containerized environments.

Set the LOG_LEVEL environment variable to change the minimum level. The default is info. Valid levels follow Pino’s defaults: fatal, error, warn, info, debug, trace, silent.