NestJS Winstonでのログ出力(アクセスログ)
NestJSプロジェクト作成
npm i -g @nestjs/cli
nest new "プロジェクト名"
Winstonインストール
yarn add winston
yarn add winston-daily-rotate-file # rotation用
カスタムLoggerのモジュール作成
nest g mo customLogger
アクセスログのservice作成
custom-logger/access-logger.service.ts
import { Injectable, LoggerService } from '@nestjs/common';
import { createLogger, format, Logger } from 'winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
import * as Transport from 'winston-transport';
@Injectable()
export class AccessLoggerService implements LoggerService {
logger: Logger;
constructor() {
/** ローテーション */
const logTransFormStream = new DailyRotateFile({
level: 'info',
dirname: `log/access/`,
filename: `log_%DATE%.log`,
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxFiles: '1d',
});
// ログローテンションするタイミングのイベント
// eslint-disable-next-line @typescript-eslint/no-empty-function
logTransFormStream.on('rotate', () => {});
const t: Transport[] = [logTransFormStream];
this.logger = createLogger({
format: format.combine(format.timestamp(), format.json()),
transports: t,
});
}
log(message: string) {
this.logger.info(message);
}
error(message: string) {
this.logger.error(message);
}
warn(message: string) {
this.logger.warn(message);
}
debug(message: string) {
this.logger.debug(message);
}
}
※rotateのオプション関連は下記url参照
https://github.com/winstonjs/winston-daily-rotate-file
### ログ出力の確認
custom-logger/custom-logger.module.ts
import { Module } from '@nestjs/common';
import { AccessLoggerService } from './access-logger.service';
@Module({
providers: [AccessLoggerService],
exports: [AccessLoggerService],
})
export class CustomLoggerModule {}
app.service.ts
import { Injectable } from '@nestjs/common';
import { AccessLoggerService } from './custom-logger/access-logger.service';
@Injectable()
export class AppService {
constructor(private readonly accessLogger: AccessLoggerService) {}
getHello(): string {
this.accessLogger.log('test');
return 'Hello World!';
}
}
yarn start:dev
ログ確認
アクセス時に自動でlogを出力するように修正 ※inspectorを使用
interceptors/logging.inspector.ts
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Request } from 'express';
import { catchError, Observable, throwError } from 'rxjs';
import { AccessLoggerService } from 'src/custom-logger/access-logger.service';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
constructor(private readonly accessLogger: AccessLoggerService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest<Request>();
const requestData = {
ip: request.ip,
method: request.method,
url: request.url,
body: request.body || {},
};
this.accessLogger.log(JSON.stringify(requestData));
return next.handle().pipe(
catchError((err) => {
// Exceptionのログを出力したい場合はここで実装
// this.errorLogger.error({
// ...requestData,
// status: err?.status ?? err?.statusCode ?? 500,
// message: err?.message ?? '',
// });
return throwError(() => new Error(err));
}),
);
// .pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
}
}
app.module.ts
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CustomLoggerModule } from './custom-logger/custom-logger.module';
import { LoggingInterceptor } from './interceptors/logging.inspector';
@Module({
imports: [CustomLoggerModule],
controllers: [AppController],
providers: [
AppService,
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
],
})
export class AppModule {}
テストで追加したログを削除する
app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}