WinstonでNestJSのロガーをカスタマイズしてみた

# はじめに

今回は、Node.jsのログ出力ライブラリである winston を使用して、NestJS用にログファイルやコンソールログをカスタマイズしていきたいと思います。
ログファイルは日付毎にローテートしたいので、winston-daily-rotate-file というライブラリも使用します。
※NestJSのプロジェクトがすでに作成済みであることを前提にしています。

# ログ出力ライブラリをインストール
NestJSのプロジェクト内で、winston と winston-daily-rotate-file をインストールします。
```bash
$ npm i winston winston-daily-rotate-file
```

# Service と Module を生成
NestJS CLI で、logging.service.ts と logging.module.ts を生成します。
```bash
$ nest g service logging
$ nest g module logging
```

ファイルが生成されたら、まずは logging.service.ts に、winstonを使用したコードを書いていきます。

```typescript:src/logging/logging.service.ts
import { Injectable, LoggerService } from '@nestjs/common';
import * as winston from 'winston';
import * as winstonDailyRotateFile from 'winston-daily-rotate-file';

@Injectable()
export class LoggingService implements LoggerService {
  logger: winston.Logger;

  constructor() {
    const logger = winston.createLogger({
      format: winston.format.combine(
        winston.format.timestamp({
          format: 'YYYY-MM-DD HH:mm:ss', // タイムスタンプのフォーマットを変える
        }),
        winston.format.errors({ stack: true }), // エラー時はスタックトレースを出力する
        winston.format.printf(
          (info) => `[${info.timestamp}] [${info.level}] ${info.message}`, // 出力内容をカスタマイズする
        ),
      ),
      transports: [
        // アクセスログの出力先設定
        new winstonDailyRotateFile({
          level: 'debug', // debugを指定すると、debug以上のログが出力される
          datePattern: 'YYYY-MM-DD', // 'YYYY-MM-DD'に設定すると、ログファイルが日付毎に作られる
          filename: 'application-%DATE%.log', // 保存先ファイル名(上記のdatePatternが含まれる)
          dirname: 'logs', // ログファイルの保存先ディレクトリ名
          maxSize: '20m', // ローテートするファイルの最大サイズ
          maxFiles: '30d', // 保存するログの最大数(日数を使う場合は接尾辞として'd'を追加)
        }),
        // エラーログの出力先設定
        new winstonDailyRotateFile({
          level: 'error',
          datePattern: 'YYYY-MM-DD',
          filename: 'error-%DATE%.log',
          dirname: 'logs',
          maxSize: '20m',
          maxFiles: '30d',
        }),
      ],
    });

    // 本番環境以外ではコンソールにも出力する
    if (process.env.NODE_ENV !== 'production') {
      logger.add(
        new winston.transports.Console({
          level: 'debug',
          format: winston.format.combine(
            winston.format.colorize(), // ログを色付けする
            winston.format.simple(), // フォーマットをsimpleにする
          ),
        }),
      );
    }

    this.logger = logger;
  }

  // NestJS標準のLoggerServiceをimplementsしているため、log() error() warn() debug() verbose()を実装する
  log(message: string) {
    this.logger.log({
      level: 'info',
      message: `${message}`,
    });
  }

  error(message: string, trace: string) {
    this.logger.log({
      level: 'error',
      message: `${message}:${trace}`,
    });
  }

  warn(message: string) {
    this.logger.log({
      level: 'warn',
      message: `${message}`,
    });
  }

  debug(message: string) {
    this.logger.log({
      level: 'debug',
      message: `${message}`,
    });
  }

  verbose(message: string) {
    this.logger.log({
      level: 'verbose',
      message: `${message}`,
    });
  }
}

```

次は、logging.module.ts に LoggingService の設定を行います。


```typescript:src/logging/logging.module.ts
import { Module } from '@nestjs/common';

import { LoggingService } from './logging.service';

@Module({
  providers: [LoggingService],
  exports: [LoggingService],
})
export class LoggingModule {}
```

# カスタムロガーをデフォルトに設定する
main.tsを以下のように修正して、今回作成したカスタムロガーの設定を行います。
```typescript:src/main.ts
import { AppModule } from './app.module';
import { LoggingService } from './logging/logging.service';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: new LoggingService(), // ここに追加
  });

  await app.listen(3000);
}

bootstrap();
```

# カスタムロガーを試してみる
AppService の getHello() でログを出力してみたいと思います。
```typescript:src/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 {
    // ログを出力してみる
    this.logger.debug('Debug Message');
    this.logger.log('Info Message');
    this.logger.warn('Warn Message');
    this.logger.error('Error Message', 'error in AppService');

    return 'Hello World!';
  }
}

```

では localhost:3000 にアクセスしてみます。
すると、以下のコンソールログが出力されました。

```text
debug: Debug Message {"timestamp":"YYYY-MM-DD HH:mm:ss"}
info: Info Message {"timestamp":"YYYY-MM-DD HH:mm:ss"}
warn: Warn Message {"timestamp":"YYYY-MM-DD HH:mm:ss"}
error: Error Message:error in AppService {"timestamp":"YYYY-MM-DD HH:mm:ss"}
```

logsディレクトリには、applicationログとerrorログの2つのファイルが出力されました。
それぞれのファイルの内容は以下の通りです。
```text:logs/application-{YYYY-MM-DD}.log
[YYYY-MM-DD HH:mm:ss] [debug] Debug Message
[YYYY-MM-DD HH:mm:ss] [info] Info Message
[YYYY-MM-DD HH:mm:ss] [warn] Warn Message
[YYYY-MM-DD HH:mm:ss] [error] Error Message:error in AppService
```

```text:logs/error-{YYYY-MM-DD}.log
[YYYY-MM-DD HH:mm:ss] [error] Error Message:error in AppService
```
errorログの方には error レベルのログのみ出力されていることが確認できるかと思います。

# おわりに
このように、特に前知識がなくても手軽にカスタムロガーを作成することができました。
ログに必要な情報というのはそれぞれの案件ごとに違ってくるものですので、必要に応じて自由にカスタマイズできるのが良いですね。