NestJS Logging

Logging records what your application does at runtime — which requests it received, which errors occurred, how long operations took, and what decisions the code made. Without logs, diagnosing production bugs requires guesswork. With structured logs, you trace any issue to its exact cause in minutes. NestJS ships a built-in logger and supports custom loggers and third-party libraries like Winston and Pino.

The Built-In Logger

NestJS's Logger class is ready to use without any installation:

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

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

  async findOne(id: number) {
    this.logger.log(`Fetching user #${id}`);

    const user = await this.userRepository.findOne({ where: { id } });

    if (!user) {
      this.logger.warn(`User #${id} not found`);
      throw new NotFoundException(`User #${id} not found`);
    }

    this.logger.debug(`Found user: ${user.email}`);
    return user;
  }
}

Log Levels

Level     | Method          | When to Use
----------|-----------------|--------------------------------------------
verbose   | logger.verbose()| Very detailed tracing (disabled in prod)
debug     | logger.debug()  | Debugging info (disabled in prod)
log       | logger.log()    | General info (request started, item saved)
warn      | logger.warn()   | Recoverable issues (missing optional data)
error     | logger.error()  | Errors that need attention (exceptions, DB failures)
fatal     | logger.fatal()  | Critical failures (app cannot continue)

Configuring Log Levels

// main.ts — enable only log, warn, error in production
const app = await NestFactory.create(AppModule, {
  logger:
    process.env.NODE_ENV === 'production'
      ? ['log', 'warn', 'error', 'fatal']
      : ['verbose', 'debug', 'log', 'warn', 'error', 'fatal'],
});

Log Output Format

Built-in logger output:
[Nest] 12345  - 01/15/2025, 9:00:00 AM  LOG [UsersService] Fetching user #42
[Nest] 12345  - 01/15/2025, 9:00:01 AM  WARN [UsersService] User #999 not found
[Nest] 12345  - 01/15/2025, 9:00:02 AM  ERROR [UsersService] DB connection failed

Format breakdown:
  [Nest]        - framework prefix
  12345         - process ID
  timestamp     - when it happened
  LOG/WARN/ERROR - severity level
  [UsersService] - context (class name)
  message        - what happened

Logging Errors With Stack Traces

try {
  await this.userRepository.save(user);
} catch (error) {
  this.logger.error(
    `Failed to save user: ${error.message}`,
    error.stack,         // second argument = stack trace
  );
  throw new InternalServerErrorException('Could not save user');
}

Custom Logger with Winston

Winston is a popular logging library that supports log rotation, structured JSON output, and multiple transports (console, file, external services):

npm install nest-winston winston

// app.module.ts
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';

@Module({
  imports: [
    WinstonModule.forRoot({
      transports: [
        new winston.transports.Console({
          format: winston.format.combine(
            winston.format.timestamp(),
            winston.format.colorize(),
            winston.format.printf(({ level, message, timestamp, context }) =>
              `${timestamp} [${context}] ${level}: ${message}`
            ),
          ),
        }),
        new winston.transports.File({
          filename: 'logs/error.log',
          level: 'error',
        }),
        new winston.transports.File({
          filename: 'logs/combined.log',
        }),
      ],
    }),
  ],
})
export class AppModule {}

// main.ts — use Winston as the app logger
const app = await NestFactory.create(AppModule, {
  logger: WinstonModule.createLogger({ ... }),
});

Request Logging Middleware

Log every incoming request centrally using middleware instead of adding logs to every controller:

@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
  private logger = new Logger('HTTP');

  use(req: Request, res: Response, next: NextFunction) {
    const { method, originalUrl, ip } = req;
    const start = Date.now();

    res.on('finish', () => {
      const { statusCode } = res;
      const duration = Date.now() - start;
      this.logger.log(`${method} ${originalUrl} ${statusCode} ${duration}ms - ${ip}`);
    });

    next();
  }
}

Structured JSON Logging for Production

// Structured log example (JSON format for log aggregators):
{
  "level": "error",
  "timestamp": "2025-01-15T09:00:00.000Z",
  "context": "UsersService",
  "message": "Failed to save user",
  "userId": 42,
  "error": "duplicate key value violates unique constraint",
  "requestId": "abc-123"
}

JSON logs feed directly into log management platforms like Datadog, Elastic Stack, or AWS CloudWatch, which index every field for fast searching. When a bug appears in production at 3 AM, searching for requestId: "abc-123" shows every log line from that specific request across all services — an invaluable debugging tool that only exists if you log consistently and in a structured format.

Leave a Comment

Your email address will not be published. Required fields are marked *