NestJS アクセスログ出力

# NestJS　Winstonでのログ出力(アクセスログ)

### NestJSプロジェクト作成
``` bin/bash
npm i -g @nestjs/cli
nest new "プロジェクト名"
```

### Winstonインストール
``` bin/bash
yarn add winston
yarn add winston-daily-rotate-file # rotation用
```

### カスタムLoggerのモジュール作成
``` bin/bash
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!';
  }
}
```
``` bash
yarn start:dev
```

http:localhost:3000にアクセス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296391/86fcd63f-615f-5f00-3d36-7e4d14a15025.png)

### ログ確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296391/b1dbe03a-2fd6-41ee-f55e-35d2738b327c.png)


## アクセス時に自動で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!';
  }
}
```

## 再度アクセスする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296391/5900ecf5-7f52-b598-3383-eaa8a05baf54.png)

### ログ確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296391/22cfc8bc-b062-8545-a2f4-cc9caf5d0cac.png)