Nest Provides a default logger and that’s fine when developing.
But if you want to go further, I recommend using winston, and with nest-winston package we can easily replace the default logger.
Installing nest-winston
npm install --save winston
npm install --save nest-winston
Using winston logger instead of NestJS’s one
We configure the logger used by our App when bootstrapping:
main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import { transports, format } from 'winston';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger({
transports: [
// let's log errors into its own file
new transports.File({
filename: `logs/error.log`,
level: 'error',
format: format.combine(format.timestamp(), format.json()),
}),
// logging all level
new transports.File({
filename: `logs/combined.log`,
format: format.combine(format.timestamp(), format.json()),
}),
// we also want to see logs in our console
new transports.Console({
format: format.combine(
format.cli(),
format.splat(),
format.timestamp(),
format.printf((info) => {
return `${info.timestamp} ${info.level}: ${info.message}`;
}),
),
}),
],
}),
});
await app.listen(3000);
}
bootstrap();
This configuration will output:
- error logs into logs/error.log
- all logs (info, warning, debug, error…) into logs/combined.log
- also also logs level into the console.
Nice! But what about file rotation ? Let’s see it now ↓
Adding log rotation
Install winston-daily-rotate-file package
npm install winston-daily-rotate-file
Now, let’s change main.ts to add log rotation:
// we need to import it to make transports.DailyRotate is available
import 'winston-daily-rotate-file';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger({
transports: [
// file on daily rotation (error only)
new transports.DailyRotateFile({
// %DATE will be replaced by the current date
filename: `logs/%DATE%-error.log`,
level: 'error',
format: format.combine(format.timestamp(), format.json()),
datePattern: 'YYYY-MM-DD',
zippedArchive: false, // don't want to zip our logs
maxFiles: '30d', // will keep log until they are older than 30 days
}),
// same for all levels
new transports.DailyRotateFile({
filename: `logs/%DATE%-combined.log`,
format: format.combine(format.timestamp(), format.json()),
datePattern: 'YYYY-MM-DD',
zippedArchive: false,
maxFiles: '30d',
}),
new transports.Console({
format: format.combine(
format.cli(),
format.splat(),
format.timestamp(),
format.printf((info) => {
return `${info.timestamp} ${info.level}: ${info.message}`;
}),
),
}),
],
}),
});
await app.listen(3000);
}
That it for logs rotation.
Now at the root of your app when you run it you should see a folder named “logs” containing `YYYY-MM-DD-error.log` and `YYYY-MM-DD-combined.log`. YYYY-MM-DD being replaced by the actual date. So as I’m writing this post we are the 10th of mars 2023, my error log file will be named : 2023–03–10-error.log
Logging 404 , 401, 400 errors
By default Nest doesn’t log Bad Request, 401 Unauthorized, nor 405 Method Not Allowed. And that’s a shame 😛!
The simplest way I found to get those logged is to use a middleware:
request-logger.middleware.ts
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
private readonly logger = new Logger();
use(req: Request, res: Response, next: NextFunction) {
res.on('finish', () => {
const statusCode = res.statusCode;
if (statusCode === 401 || statusCode === 404 || statusCode === 405) {
this.logger.warn(`[${req.method}] ${req.url} - ${statusCode}`);
}
});
next();
}
}
Now let’s apply our middleware to our app module:
app.module.ts
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { RequestLoggerMiddleware } from '../middleware/request-logger.middleware';
@Module({
controllers: [AppController],
providers: [],
})
export class AppModule {
// let's add a middleware on all routes
configure(consumer: MiddlewareConsumer) {
consumer.apply(RequestLoggerMiddleware).forRoutes('*');
}
}
Conclusion
File logging isn’t too hard to setup and improves our NestJs API by:
- helping with debugging
- monitoring performance (if you put metrics you can track)
- improving security by monitor unauthorized access attempts
That’s it! Thank you for reading