NestJSでLoggerライブラリwinstonとwinston-daily-rotate-fileを使って、カスタムロガーを作成します!
NestJSのLoggerをwinstonでカスタムする
NestJSのLoggerに関するリファレンスには、以下のような記載があります。
For more advanced logging functionality, you can make use of any Node.js logging package, such as Winston, to implement a completely custom, production grade logging system.
https://docs.nestjs.com/techniques/logger
DeepLで翻訳すると…
より高度なロギング機能については、Winston などの任意の Node.js ロギング・パッケージを使用して、完全にカスタムでプロダクション・グレードのロギング・システムを実装することができます。
製品に組み込むような高度なロギングはカスタムしてくださいね、
ライブラリはwinstonがおすすめだよ(そう書いてはないけど笑)とのことです。
winston
Node.jsのLoggerライブラリであるwinstonを使用します。
他にもライブラリはありますが、NestJSで名前が挙がっているので選定しました。
TypeScript製のライブラリなのでNestJSとの相性も良いのかなと思います。
winston-daily-rotate-file
今回は日付毎にログファイルをローテートしたいので、
winston-daily-rotate-fileというライブラリもインストールしていきます。
カスタムロガーを作成する – customizing Logger
npm install
必要なライブラリをインストールします。
npm i winston winston-daily-rotate-file
moduleとserviceを作成する
NestJSを触ったことのある方はよく使うであろうコマンドで、moduleとserviceを作成します。
nest g module logging
nest g service logging
Web APIのエンドポイントを特に設ける必要がないので、コントローラは作成しません。
logging.service.tsを実装する
logging.service.tsでロガーを実装していきます。
import { Injectable, LoggerService } from '@nestjs/common';
import { createLogger, format, Logger, transports } from 'winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
@Injectable()
export class LoggingService implements LoggerService {
logger: Logger;
constructor() {
// infoログ(通常ログ)の出力先を定義
const applicationLogTransport: DailyRotateFile = new DailyRotateFile({
level: 'info',
filename: 'application-%DATE%.log',
dirname: 'logs/application',
datePattern: 'YYYYMMDD',
maxFiles: '7d',
});
// errorログの出力先を定義
const errorLogTransport: DailyRotateFile = new DailyRotateFile({
level: 'error',
filename: 'error-%DATE%.log',
dirname: 'logs/error',
datePattern: 'YYYYMMDD',
maxFiles: '7d',
});
this.logger = createLogger({
level: 'info',
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.errors({ stack: true }), // errorログではstackを表示
format.splat(),
format.json(),
),
defaultMeta: { service: 'logging', funcName: '' }, // デフォルトの出力項目を指定
transports: [applicationLogTransport, errorLogTransport],
});
// envでログをコンソール出力するか決定する
// コンソールへの出力を追加しないと開発がしづらいので注意
if (process.env.ENV_NAME !== 'prod') {
this.logger.add(
new transports.Console({
level: 'debug',
format: format.combine(format.colorize(), format.simple()),
}),
);
}
}
// デフォルトのLoggerServiceを継承しているため、log・error・warnは実装しないといけない
log(message: string, service: string, funcName: string): void {
this.logger.info(message, {
service: service,
funcName: funcName,
notDefaultMeta: 'デフォルトにないの情報も出力可能です!',
});
}
error(message: string, err: Error, service: string, funcName: string): void {
this.logger.error(message, err, {
service: service,
funcName: funcName,
});
}
warn(message: string): void {
this.logger.warn(message);
}
}
logging.module.ts
exportsを定義します。
import { Module } from '@nestjs/common';
import { LoggingService } from './logging.service';
@Module({
providers: [LoggingService],
exports: [LoggingService], // 追加する
})
export class LoggingModule {}
main.tsでデフォルトからカスタムロガーに切り替える
main.tsを修正する。
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingService } from './logging/logging.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: false, // loggerをオフ -> デバッグ時のコンソール出力がなくなる
});
// カスタムロガーをセットする
app.useLogger(app.get(LoggingService));
await app.listen(3000);
}
bootstrap();
コンパイル失敗などのログが出力されないため、logger: false
は開発時には設定しないほうが無難です。
app.service.tsでloggerを呼び出す
app.service.tsで試しにログを出力してみます。
import { Injectable } from '@nestjs/common';
import { LoggingService } from './logging/logging.service';
@Injectable()
export class AppService {
constructor(private readonly logger: LoggingService) {}
getHello(): string {
// info
this.logger.log('関数を実行します。', 'app', 'getHello');
// error
this.logger.error(
'エラーが発生しました。',
new Error('error in app.service'),
'app',
'getHello',
);
return 'Hello World!';
}
}
ログを出力させる
NestJSアプリを起動します。
npm run start:dev
コンソールにログが出力されていることも確認しておきましょう!
localhost:3000
にアクセスし、ログを出力させます。
ログを確認する
infoログ
infoレベル以上のログが出力対象であるため、errorログも出力されています。
{"level":"info","message":"AppController {/}:","notDefaultMeta":"デフォルトにないの情報も出力可能です!","service":"RoutesResolver","timestamp":"2022-03-06 22:51:52"}
{"level":"info","message":"Mapped {/, GET} route","notDefaultMeta":"デフォルトにないの情報も出力可能です!","service":"RouterExplorer","timestamp":"2022-03-06 22:51:52"}
{"level":"info","message":"Nest application successfully started","notDefaultMeta":"デフォルトにないの情報も出力可能です!","service":"NestApplication","timestamp":"2022-03-06 22:51:52"}
{"funcName":"getHello","level":"info","message":"関数を実行します。","notDefaultMeta":"デフォルトにないの情報も出力可能です!","service":"app","timestamp":"2022-03-06 22:52:04"}
{"funcName":"getHello","level":"error","message":"エラーが発生しました。 error in app.service","service":"app","stack":"Error: error in app.service\n at AppService.getHello (/sample/nestjs-logging/src/app.service.ts:15:13)\n at AppController.getHello (/sample/nestjs-logging/src/app.controller.ts:10:32)\n at /sample/nestjs-logging/node_modules/@nestjs/core/router/router-execution-context.js:38:29\n at InterceptorsConsumer.intercept (/sample/nestjs-logging/node_modules/@nestjs/core/interceptors/interceptors-consumer.js:11:20)\n at /sample/nestjs-logging/node_modules/@nestjs/core/router/router-execution-context.js:46:60\n at /sample/nestjs-logging/node_modules/@nestjs/core/router/router-proxy.js:9:23\n at Layer.handle [as handle_request] (/sample/nestjs-logging/node_modules/express/lib/router/layer.js:95:5)\n at next (/sample/nestjs-logging/node_modules/express/lib/router/route.js:137:13)\n at Route.dispatch (/sample/nestjs-logging/node_modules/express/lib/router/route.js:112:3)\n at Layer.handle [as handle_request] (/sample/nestjs-logging/node_modules/express/lib/router/layer.js:95:5)","timestamp":"2022-03-06 22:52:04"}
errorログ
errorログのみが出力されています。
format.errors({ stack: true })
でエラーの際はスタックトレースを出力するようにしています。
{"funcName":"getHello","level":"error","message":"エラーが発生しました。 error in app.service","service":"app","stack":"Error: error in app.service\n at AppService.getHello (/sample/nestjs-logging/src/app.service.ts:15:13)\n at AppController.getHello (/sample/nestjs-logging/src/app.controller.ts:10:32)\n at /sample/nestjs-logging/node_modules/@nestjs/core/router/router-execution-context.js:38:29\n at InterceptorsConsumer.intercept (/sample/nestjs-logging/node_modules/@nestjs/core/interceptors/interceptors-consumer.js:11:20)\n at /sample/nestjs-logging/node_modules/@nestjs/core/router/router-execution-context.js:46:60\n at /sample/nestjs-logging/node_modules/@nestjs/core/router/router-proxy.js:9:23\n at Layer.handle [as handle_request] (/sample/nestjs-logging/node_modules/express/lib/router/layer.js:95:5)\n at next (/sample/nestjs-logging/node_modules/express/lib/router/route.js:137:13)\n at Route.dispatch (/sample/nestjs-logging/node_modules/express/lib/router/route.js:112:3)\n at Layer.handle [as handle_request] (/sample/nestjs-logging/node_modules/express/lib/router/layer.js:95:5)","timestamp":"2022-03-06 22:52:04"}
まとめ
ログファイル出力の基本的な部分の実装はできました。
あとはログに必要な情報を残すように、meta情報をカスタムしてみてください!
コメント