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();
}
}
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} -