Logging is a crucial part of any application, and NestJS provides a built-in logging mechanism that can be easily integrated into your application. In this module, we will explore the NestJS Logger, how it works out-of-the-box, and how you can create and use custom loggers to improve your application’s logging strategy.
Table of Contents
- Introduction
- Understanding NestJS Logger
- Using the Built-in Logger
- Creating a Custom Logger
- Configuring Log Levels
- Advanced Logging with Custom Loggers
- Best Practices for Logging in NestJS
- Conclusion
Introduction
In modern applications, logging is essential for monitoring, debugging, and maintaining the health of an application. NestJS comes with a built-in logger that makes it easy to log information at different levels (e.g., log
, error
, warn
, debug
, verbose
). However, in certain cases, you might want to implement a custom logging solution to suit your application’s specific needs.
This module covers how to use the built-in NestJS Logger, how to create your own custom loggers, and how to configure logging levels to control the verbosity of your logs.
Understanding NestJS Logger
NestJS uses a logging system to output different kinds of logs (like log
, warn
, error
, etc.) and provides a Logger
service that you can use in any part of your application. By default, the built-in logger is useful for general-purpose logging needs.
Features of the NestJS Logger:
- It supports different log levels:
log
,warn
,error
,debug
, andverbose
. - It provides automatic logging of HTTP requests.
- It integrates seamlessly with NestJS’s core components such as controllers, services, and guards.
Using the Built-in Logger
NestJS provides the Logger
class from the @nestjs/common
package. You can use it to log messages at various levels, and it supports multiple output formats (console, file, external loggers, etc.).
Basic Usage
To log messages with the built-in Logger
, you can import it and use its static methods or instantiate it.
typescriptCopyEditimport { Logger } from '@nestjs/common';
@Controller('cats')
export class CatsController {
private readonly logger = new Logger(CatsController.name);
@Get()
findAll() {
this.logger.log('Fetching all cats'); // Log info-level message
return [];
}
@Post()
create() {
this.logger.warn('Creating a new cat'); // Log warning-level message
}
@Get(':id')
findOne(@Param('id') id: string) {
try {
// Business logic
} catch (error) {
this.logger.error(`Error occurred while fetching cat with ID ${id}`, error.stack); // Log error-level message
}
}
}
Available Log Levels
The following methods are available in the Logger
class to output logs at various levels:
log(message: string)
: Logs a general informational message.warn(message: string)
: Logs a warning message.error(message: string, trace?: string)
: Logs an error message along with an optional stack trace.debug(message: string)
: Logs a debug message (typically useful for development).verbose(message: string)
: Logs a detailed verbose message for detailed application state information.
Creating a Custom Logger
While the built-in Logger
class works well for many cases, there might be scenarios where you need more control over the logging behavior, such as writing logs to a file, integrating with a third-party logging service, or formatting logs differently.
To create a custom logger, you can extend the built-in Logger
class and override its methods to implement your custom logic.
Example: Custom Logger Implementation
typescriptCopyEditimport { LoggerService, Injectable, LogLevel } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
@Injectable()
export class CustomLogger implements LoggerService {
private logLevels: LogLevel[] = ['log', 'warn', 'error', 'debug', 'verbose'];
log(message: string) {
this.writeToFile('log', message);
}
warn(message: string) {
this.writeToFile('warn', message);
}
error(message: string, trace: string) {
this.writeToFile('error', `${message}\nTrace: ${trace}`);
}
debug(message: string) {
this.writeToFile('debug', message);
}
verbose(message: string) {
this.writeToFile('verbose', message);
}
private writeToFile(level: LogLevel, message: string) {
const logMessage = `${new Date().toISOString()} [${level.toUpperCase()}]: ${message}\n`;
const logFilePath = path.join(__dirname, '..', 'logs', `${level}.log`);
if (!fs.existsSync(path.dirname(logFilePath))) {
fs.mkdirSync(path.dirname(logFilePath), { recursive: true });
}
fs.appendFileSync(logFilePath, logMessage);
}
}
In this example, the CustomLogger
class writes logs to separate files based on the log level (log.log
, warn.log
, error.log
, etc.). You can customize it further based on your needs, such as integrating with external logging services like Winston or Loggly.
Registering the Custom Logger
To use your custom logger in your NestJS application, you need to register it globally or inject it where needed.
- Globally Registering the Custom Logger (in
main.ts
):
typescriptCopyEditimport { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { CustomLogger } from './common/logger/custom-logger.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useLogger(app.get(CustomLogger)); // Use the custom logger globally
await app.listen(3000);
}
bootstrap();
- Injecting the Custom Logger in a Service or Controller:
typescriptCopyEdit@Controller('cats')
export class CatsController {
constructor(private readonly logger: CustomLogger) {}
@Get()
findAll() {
this.logger.log('Fetching all cats');
return [];
}
}
Configuring Log Levels
NestJS allows you to configure which log levels are active based on the environment. For example, in a development environment, you may want verbose logs, while in production, you may want to only log warnings and errors.
Example: Enabling/Disabling Log Levels
typescriptCopyEditimport { Logger } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useLogger(new CustomLogger());
const environment = process.env.NODE_ENV || 'development';
if (environment === 'production') {
Logger.overrideLogger(['error', 'warn']); // Only log errors and warnings in production
}
await app.listen(3000);
}
bootstrap();
This allows you to have fine-grained control over the verbosity of logs based on the environment.
Advanced Logging with Custom Loggers
For more advanced logging scenarios, you can integrate external logging libraries like Winston or Pino. These libraries offer features like log rotation, centralized logging, and remote logging services.
Example: Using Winston with NestJS
- Install
winston
:
bashCopyEditnpm install winston
- Implement the custom logger with Winston:
typescriptCopyEditimport { LoggerService, Injectable } from '@nestjs/common';
import * as winston from 'winston';
@Injectable()
export class WinstonLogger implements LoggerService {
private logger: winston.Logger;
constructor() {
this.logger = winston.createLogger({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple(),
),
}),
new winston.transports.File({ filename: 'app.log' }),
],
});
}
log(message: string) {
this.logger.info(message);
}
warn(message: string) {
this.logger.warn(message);
}
error(message: string, trace: string) {
this.logger.error(`${message}\n${trace}`);
}
debug(message: string) {
this.logger.debug(message);
}
verbose(message: string) {
this.logger.verbose(message);
}
}
Now you can inject this WinstonLogger
into your application like any other logger.
Best Practices for Logging in NestJS
- Use Appropriate Log Levels: Be mindful of the log levels (
log
,warn
,error
,debug
,verbose
). Usedebug
andverbose
for development and troubleshooting, anderror
andwarn
for production environments. - Centralize Logs: Use centralized logging solutions like Winston or external services for better log management, especially in microservice architectures.
- Avoid Sensitive Information: Never log sensitive data like passwords, tokens, or personal user data.
- Rotate Logs: If you’re logging to files, set up log rotation to prevent large log files from accumulating and consuming disk space.
- Handle Errors Gracefully: Use logging to capture detailed error traces and stack information for easier debugging.
Conclusion
In this module, we explored the NestJS Logger and how to create custom loggers to meet the specific logging needs of your application. We also looked at how to configure logging levels based on the environment and how to integrate more advanced logging solutions like Winston. Proper logging is essential for maintaining a healthy and scalable application, and with NestJS’s flexible logging system, you can implement logging that fits your project’s requirements.