🐯

NestJS+Winstonでカスタムロガーを作る

2021/05/19に公開

概要

NestJSWinstonを組み合わせて、ロガーをカスタマイズします。

Winstonをカスタマイズすることで、ログファイルやコンソールログだけでなく、New RelicDATADOGなどにも柔軟に連携できます。

セットアップ

NestJSのプロジェクトを作成する

NestJSのプロジェクトを作成します。 (Setup参照)

$ npm i -g @nestjs/cli
$ nest new project-name

作成したプロジェクトに移動して、コマンドを実行します。

$ cd project-name
$ npm run start

ブラウザでhttp://localhost:3000にアクセスします。
"Hello World!"と表示されたら、セットアップは完了です。

Winstonをインストールする

プロジェクト内にWinstonをインストールします。

$ npm i winston

カスタムロガーを設定する

NestJSのカスタムロガーについては、Custom implementationを参照

カスタムロガーを実装する

カスタムロガーのファイルを作成する

NestJS CLIModuleServiceを作ります。

$ nest g s myLogger 
$ nest g mo myLogger

下のように、ModuleServiceが作成されます。

project-name
 |-- src
      |-- my-logger
           |-- my-logger.module.ts
	   |-- my-logger.service.spec.ts (使いません; Serviceのテストコード)
	   |-- my-logger.service.ts

カスタムロガーのソースコードを書く

my-logger.service.tsにコードを実装します。
今回はWinstonのUsageのソースコードをベースに、タイムスタンプを追加しました。

LoggerServiceをimplementsして、log() error() warn() debug() verbose()を実装します。
それぞれのメソッドの中身は、Winstonのロガーを設定しています。

my-logger.service.ts
import {LoggerService} from '@nestjs/common';
import {createLogger, format, Logger, transports} from 'winston';

export class MyLoggerService implements LoggerService {
    logger: Logger;

    constructor() {
        this.logger = createLogger({
            level: 'info',
            format: format.combine(format.timestamp(), format.json()),
            defaultMeta: {service: 'user-service'},
            transports: [
                //
                // - Write all logs with level `error` and below to `error.log`
                // - Write all logs with level `info` and below to `combined.log`
                //
                new transports.File({filename: 'error.log', level: 'error'}),
                new transports.File({filename: 'combined.log'}),
            ],
        });
    }

    log(message: string) {
        this.logger.info(message);
    }

    error(message: string, trace: string) {
        this.logger.error(message, trace)
    }

    warn(message: string) {
        this.logger.warn(message);
    }

    debug(message: string) {
        this.logger.debug(message);
    }

    verbose(message: string) {
        this.logger.verbose(message)
    }
}

Moduleを設定する

src/my-logger/my-logger.module.tsにカスタムロガーを設定します。

my-logger.module.ts
import { Module } from '@nestjs/common';
import {MyLoggerService} from "./my-logger.service";

@Module({
    providers: [MyLoggerService],
    exports: [MyLoggerService]
})
export class MyLoggerModule {}

カスタムロガーをデフォルトに設定する

src/main.tsを下記に書き換えます。
これでNestJS標準のロガーは使われず、カスタマイズしたロガーがデフォルトになります。

main.ts
import {NestFactory} from '@nestjs/core';
import {AppModule} from './app.module';
import {MyLoggerService} from "./my-logger/my-logger.service";

async function bootstrap() {
    const app = await NestFactory.create(AppModule, {
        logger: false,
    });
    app.useLogger(app.get(MyLoggerService))
    await app.listen(3000);
}

bootstrap();

ログを出力する

app.service.tsにロガーを設定します。

app.service.ts
import {Injectable, Logger} from '@nestjs/common';

@Injectable()
export class AppService {
    private readonly logger = new Logger();

    getHello(): string {
        this.logger.log('Hello World!');
        return 'Hello World!';
    }
}

http://localhost:3000にアクセスすると、combined.logにログが出力されます。

{"message":"Hello World!","level":"info","service":"user-service","timestamp":"2021-05-18T17:01:21.506Z"}

Discussion