In this blog post, we will be setting up logging in NestJS with Winston.
At the time of writing this blog, the versions used are NestJS@10.4.15
and Winston@3.17.0
Installing Winston
Let’s start by installing winston. You can use below commands to add winston
to your application.
pnpm add winston
or
npm i winston
Setting up Winston
Let us create a logging.service.ts
file in a logging folder. We will be using this file to instantiate winston and our logging service.
Below is the folder structure I use in my application.
Import winston and create a instance of logger. We will be only using single instance of this logger throughout our application.
// --file-- logging.service.ts
import winston from 'winston';
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
transports: [
new winston.transports.Console({ format: winston.format.combine(winston.format.colorize(), winston.format.simple()) }), // This will log the logging messages to console
new winston.transports.File({ filename: 'logs/combined.log' }), // This will create a log file in a logs folder
],
format: winston.format.json(), // All you logs will be exported in json format
level: 'debug',
});
Creating Logging Service
Now Let’s create a LoggingService
class in same logging.service.ts
file. To create this Logging Service we need to extend existing logging class.
import winston from 'winston';
import { LoggerService, Injectable, LogLevel } from '@nestjs/common';
const logger = winston.createLogger({
transports: [new winston.transports.Console(), new winston.transports.File({ filename: 'logs/combined.log' })],
format: winston.format.json(),
level: 'debug',
});
@Injectable()
export class AppLoggingService implements LoggerService {
log(message: any, ...optionalParams: any[]) {
logger.log('info', message, ...optionalParams);
}
error(message: any, ...optionalParams: any[]) {
logger.error(message, ...optionalParams);
}
warn(message: any, ...optionalParams: any[]) {
logger.warn(message, ...optionalParams);
}
debug(message: any, ...optionalParams: any[]) {
logger.debug(message, ...optionalParams);
}
verbose?(message: any, ...optionalParams: any[]) {
logger.verbose(message, ...optionalParams);
}
fatal?(message: any, ...optionalParams: any[]) {
logger.error(message, ...optionalParams);
}
setLogLevels?(levels: LogLevel[]) {
if (levels && levels.length > 0) {
const level = levels[0]; // Default to the first level in the array
logger.level = level;
logger.info(`Log level set to ${level}`);
}
}
}
Create Logging module
We need to export this logging service through a module so that we can access it across our nestJS application.
import { Module } from '@nestjs/common';
import { AppLoggingService } from './logging.service';
@Module({
providers: [AppLoggingService],
exports: [AppLoggingService],
})
export class LoggerModule {}
Importing The logging module
You need to import the logging module in your service
@Module({
imports: [
AuthModule,
UsersModule,
BusinessesModule,
CustomersModule,
CustomerTypesModule,
PrismaModule,
LoggerModule, // Our logging service
],
controllers: [AppController, AuthController],
providers: [AppService],
})
export class AppModule {}
Now you can access the service by using
@Injectable()
export class CustomersService {
constructor(
private readonly prisma: PrismaService,
private readonly logger: AppLoggingService, // Nest will automatically inject the service
) {}
// ...other code
async remove(businessId: string, id: string) {
try {
return await this.prisma.customer.delete({
where: {
businessId: businessId,
id: id,
},
});
} catch (err) {
if (err instanceof PrismaClientKnownRequestError) {
if (err.code == 'P2025') {
throw new NotFoundException();
}
}
this.logger.error(`Error Deleting Customer`, { err: err, args: arguments }); // Logging
throw new InternalServerErrorException();
}
}
}
Below is how it is logged
Now you have a fully configured logging system in your NestJS application using Winston. You can adjust log levels, add multiple transports, and even set up centralized logging services for better monitoring. Logging is a crucial aspect of maintaining and debugging applications, and with Winston’s powerful configuration options, you can ensure that your logs are informative, readable, and scalable.
Next Steps
- Centralized Logging: Integrate centralized logging services like Datadog, AWS CloudWatch, or Loggly for better management and analysis of logs.
- Log Rotation: Set up log rotation to prevent log files from consuming too much disk space.
- Performance Monitoring: Consider integrating logging with application performance monitoring (APM) tools for more detailed insights into your app’s behavior.
Happy coding, and happy logging! 🎉