Lesson 20-NestJS Logging

NestJS Log Levels

NestJS supports multiple log levels, including:

  • log: General information logs.
  • error: Error logs.
  • warn: Warning logs.
  • debug: Debug logs.
  • verbose: More detailed debug logs.

Log Configuration

You can control logging behavior by modifying the logger property on the app instance. For example, you can set log levels in main.ts or through global configuration.

Example Code
Set log levels in main.ts:

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // Set log levels
  app.enableLogger(['log', 'error', 'warn', 'debug', 'verbose']);
  await app.listen(3000);
}
bootstrap();

Using a Custom Logging Service: For more complex logging needs, you can create a custom logging service class and inject it into controllers or services.

logger.service.ts

import { Injectable } from '@nestjs/common';
import * as winston from 'winston';

@Injectable()
export class LoggerService {
  private logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
      new winston.transports.Console(),
      new winston.transports.File({ filename: 'combined.log' }),
    ],
  });

  log(message: string) {
    this.logger.info(message);
  }

  error(message: string, trace?: string) {
    this.logger.error(message, trace);
  }

  warn(message: string) {
    this.logger.warn(message);
  }

  debug(message: string) {
    this.logger.debug(message);
  }

  verbose(message: string) {
    this.logger.verbose(message);
  }
}

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggerService } from './logger.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService, LoggerService],
})
export class AppModule {}

Using the Logging Service in a Controller: app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { LoggerService } from './logger.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService, private readonly logger: LoggerService) {}

  @Get()
  getHello(): string {
    this.logger.log('GET request received');
    return this.appService.getHello();
  }
}

Log Output

Depending on the environment, you can choose to output logs to the console, files, or a database. In the custom logging service above, we used winston, a popular logging module, to handle log output.

Log Filtering

You can filter logs based on your needs, such as logging only errors in production environments while logging all levels in development.

Controlling Log Levels with Environment Variables

In real-world deployments, log levels are often adjusted based on the environment (e.g., development, testing, production). This can be achieved using environment variables.

Set the log level in the .env file:

LOG_LEVEL=debug

Modify logger.service.ts to read environment variables:

import { Injectable } from '@nestjs/common';
import * as winston from 'winston';
import * as process from 'process';

@Injectable()
export class LoggerService {
  private logger = winston.createLogger({
    level: process.env.LOG_LEVEL || 'info',
    format: winston.format.json(),
    transports: [
      new winston.transports.Console(),
      new winston.transports.File({ filename: 'combined.log' }),
    ],
  });

  // ... other methods ...
}

Using Winston Middleware

Winston provides various middleware to extend its functionality, such as logging to MongoDB, Redis, etc.

Install Winston’s MongoDB adapter:

npm install winston-mongodb

Modify logger.service.ts to add MongoDB support:

import { Injectable } from '@nestjs/common';
import * as winston from 'winston';
import * as mongoose from 'mongoose';
import * as winstonMongoDB from 'winston-mongodb';

@Injectable()
export class LoggerService {
  private logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
      new winston.transports.Console(),
      new winston.transports.File({ filename: 'combined.log' }),
      new winstonMongoDB({
        db: mongoose.connection.db,
        level: 'info',
        collection: 'logs',
      }),
    ],
  });

  // ... other methods ...
}

Log Formatting

You can customize the log output format to include information like timestamps or log levels.

Modify logger.service.ts to add formatting:

import { Injectable } from '@nestjs/common';
import * as winston from 'winston';
import * as moment from 'moment';

@Injectable()
export class LoggerService {
  private logger = winston.createLogger({
    level: 'info',
    format: winston.format.combine(
      winston.format.timestamp({
        format: 'YYYY-MM-DD HH:mm:ss',
      }),
      winston.format.printf(info => `${moment(info.timestamp).format()} ${info.level}: ${info.message}`),
    ),
    transports: [
      new winston.transports.Console(),
      new winston.transports.File({ filename: 'combined.log' }),
    ],
  });

  // ... other methods ...
}

Asynchronous Logging

For performance-sensitive applications, consider using asynchronous logging to avoid blocking the main thread.

Use Winston’s async format:

import { Injectable } from '@nestjs/common';
import * as winston from 'winston';

@Injectable()
export class LoggerService {
  private logger = winston.createLogger({
    level: 'info',
    format: winston.format.combine(
      winston.format.async(),
      winston.format.json(),
    ),
    transports: [
      new winston.transports.Console(),
      new winston.transports.File({ filename: 'combined.log' }),
    ],
  });

  // ... other methods ...
}
Share your love