[NestJS] LoggerMiddleware 로 로그 남기기

codeing999·2023년 7월 26일
1

NestJS

목록 보기
9/9

공식 문서 : https://docs.nestjs.com/middleware

기본 형태

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
	private logger = new Logger('HTTP');
  use(req: Request, res: Response, next: NextFunction) {
    res.on('finish', () => {
			this.logger.log(
				`${req.ip} ${req.method} ${res.statusCode}`,
				req.originalUrl,
			);
		});

    next();
  }
}

커스텀

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import Logger from './../logger';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  private logger = new Logger(process.env.NODE_ENV);
  use(req: Request, res: Response, next: NextFunction) {
    const { method, originalUrl: url, params, query, body, headers } = req;

    const originalSend = res.send;
    res.send = function (responseBody) {
      const statusCode = res.statusCode;

      // 응답 전송
      res.send = originalSend;
      res.send(responseBody);

      // 로그 남기기
      this.logger.info(
        `${method} ${url} ${statusCode}\n[REQUEST] \nParams: ${JSON.stringify(
          params,
        )}Query: ${JSON.stringify(query)}Body: ${JSON.stringify(
          body,
        )}Headers: ${JSON.stringify(
          headers,
        )}[RESPONSE]\nStatus:${statusCode}\nBody: ${responseBody}`,
      );
    }.bind(this);

    next();
  }
}
  • method, url statusCode를 일단 제일 윗줄에 남기고
  • request의 params, query, body, headers를 남기고
  • 마지막으로 response의 status와 body를 남기도록 커스텀하였다.

사용법

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .exclude('/health') // 헬스체커만 제외
      .forRoutes({ path: '*', method: RequestMethod.ALL }); // 모든 경로
  }
}

AppModule 클래스에 위와 같이 등록하면 된다.

.apply(LoggerMiddleware)
      .forRoutes('*'); //전역에 등록

이와 같이 하면 모든 경로에 미들웨어를 적용시키는 건데
나는 헬스체커로 계속 요청이 가는 /heath 경로만 제외 시켰다.

로그 예시

성공 시

2023-07-27 03:44:19] [info] [production]  : POST /counseling 201
[REQUEST]
Params: {"0":"counseling"}Query: {}Body: {"userId":1,"petId":1,"counselingDateTime":"2023-08-07 12:30:10","doctorId":1,"content":"","expense":0}Headers: {"x-forwarded-for":"58.76.183.206","x-forwarded-proto":"http","x-forwarded-port":"3000","host":"lb-nestjs4-637607277.ap-northeast-2.elb.amazonaws.com:3000","x-amzn-trace-id":"Root=1-64c16983-0d921d5b3be808701137be21","content-length":"148","content-type":"application/json","authorization":"Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.BGb2QZL70iqksC3_iGZQ3GNhJUlOtvWDxpDv1kYRAXQ","user-agent":"PostmanRuntime/7.32.3","accept":"/","postman-token":"44824dd7-c9b6-47c8-86c5-b20597fbcf01","accept-encoding":"gzip, deflate, br"}
[RESPONSE]
Status:201
Body: {"statusCode":200,"timestamp":"2023-07-27 03:44:19","message":"성공","result":{"id":2,"userName":"Mable Blanda","petName":"Jack","doctorName":"Dr. Ignacio Cronin IV","hospitalName":"Pouros - Jacobs","dateTime":"2023-08-07T12:30:10.000Z","status":"Reserved","expense":0,"content":null}}

에러났을 시

[2023-07-27 03:28:39] [error] [user.controller] - 회원가입 실패, 12345 님 미안합니다 : BadRequestException: 12345 는 이미 있는 ID 입니다.

[2023-07-27 03:28:39] [info] [production]  : POST /user/signup 400
[REQUEST]
Params: {"0":"user/signup"}Query: {}Body: {"account":"12345","password":"1234","userName":"찰스","phoneNumber":"01012345678"}Headers: {"x-forwarded-for":"58.76.183.206","x-forwarded-proto":"http","x-forwarded-port":"3000","host":"lb-nestjs4-637607277.ap-northeast-2.elb.amazonaws.com:3000","x-amzn-trace-id":"Root=1-64c165d7-15ab38951c7f41d954313b87","content-length":"119","content-type":"application/json","user-agent":"PostmanRuntime/7.32.3","accept":"/","postman-token":"c5ae1c5a-951c-4c04-aee3-86644ab36aa0","accept-encoding":"gzip, deflate, br"}[RESPONSE]
Status:400
Body: {"timestamp":"2023-07-27 03:28:39","path":"/user/signup","message":"12345 는 이미 있는 ID 입니다.","error":"Bad Request","statusCode":400}

이거는 로그 두개가 같이 남은 경우이다.
db에러 같은 에러가 났을 시에는 그 즉시 로그에 대한 에러를 남기고
미들웨어를 통해서 그 에러가 난 요청과 응답에 대한 기본적인 로그도 또 남게 된다.
두개를 같이보면 에러에 대한 정보를 상세히 알고 빠르게 해결하기 용이할 것이다.

슬랙 알림

에러와 같은 중요한 로그는 슬랙 알림으로 오게 구현하였다.

한번 더 커스텀

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import Logger from './../logger';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  private logger = new Logger(process.env.NODE_ENV);
  use(req: Request, res: Response, next: NextFunction) {
    const { method, originalUrl: url, params, query, body, headers } = req;

    const originalSend = res.send;
    res.send = function (responseBody) {
      const statusCode = res.statusCode;

      // 응답 전송
      res.send = originalSend;
      res.send(responseBody);

      // 로그 남기기
      this.logger.info(
        `${method} ${url} ${statusCode}\n ========================[REQUEST]========================\n` +
        `Params: ${JSON.stringify(params, null, 2)}\n` +
        `Query: ${JSON.stringify(query, null, 2).replace(/,/g, ',\n')}\n` +
        `Body: { \n` +
        Object.keys(body)
        .map((key) => `  "${key}": "${body[key]}"`)
        .join(",\n") +
         `\n}\n` +
        `Headers: {\n` +
        Object.keys(headers)
          .map((key) => `  "${key}": "${headers[key]}"`)
          .join(",\n") +
        `\n}\n` +
        `========================[RESPONSE]========================\n` +
        `Status:${statusCode}\n` +
        `Body: ${JSON.stringify(JSON.parse(responseBody), null, '').replace(/,/g, ',\n')}`
      );
    }.bind(this);

    next();
  }
}

로그 형식을 더 가독성 있게 변경.

[2023-07-29 00:05:10] [error] [user.controller] : BadRequestException: 12 는 이미 있는 ID 입
니다.
    at UserService.validateSignUpDto (C:\Users\mero\Desktop\projects\animalNest\dist\main.js:3069:23)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async UserService.signUp (C:\Users\mero\Desktop\projects\animalNest\dist\main.js:3042:13)
    at async UserController.signUp (C:\Users\mero\Desktop\projects\animalNest\dist\main.js:3210:28)
    at async C:\Users\mero\Desktop\projects\animalNest\node_modules\@nestjs\core\router\router-execution-context.js:46:28
    at async C:\Users\mero\Desktop\projects\animalNest\node_modules\@nestjs\core\router\router-proxy.js:9:17
======================================
metadata: 회원가입 실패, 12 님 미안합니다
[2023-07-29 00:05:10] [info] [develop] : POST /user/signup 400
 ========================[REQUEST]========================
Params: {
  "0": "user/signup"
}
Query: {}
Body: {
  "account": "12",
  "password": "1234",
  "userName": "찰스",
  "phoneNumber": "01012345678"
}
Headers: {
  "content-type": "application/json",
  "authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.BGb2QZL70iqksC3_iGZQ3GNhJUlOtvWDxpDv1kYRAXQ",
  "user-agent": "PostmanRuntime/7.32.3",
  "accept": "*/*",
  "postman-token": "cc61e1b3-35c0-4a63-bd7d-8999a6c899e8",
  "host": "127.0.0.1:3000",
  "accept-encoding": "gzip, deflate, br",
  "connection": "keep-alive",
  "content-length": "116"
}
========================[RESPONSE]========================
Status:400
Body: {"timestamp":"2023-07-29 00:05:10",
"path":"/user/signup",
"message":"12 는 이미 있는 ID 입니다.",
"error":"Bad Request",
"statusCode":400} -
profile
코딩 공부 ing..

0개의 댓글