
토이 프로젝트 진행 중 거래별로 추적번호를 만들어야 했다.
추적번호 생성
- traceId.middle.ts
import { Request, Response, NextFunction } from 'express';
import { Injectable, NestMiddleware } from '@nestjs/common';
import { randomUUID } from 'crypto';
@Injectable()
export class TraceIdMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const traceId = randomUUID();
// 추적번호를 생성하여 header에 넣는다.
req.headers['traceId'] = traceId;
next();
}
}
송수신 거래 로깅
- Logging.interceptor.ts
import { Observable } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { randomUUID } from 'crypto';
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from '@nestjs/common';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const method = request.method;
const url = request.url;
// header에서 추적번호를 찾는다.
const traceId = request.headers['traceId'];
const logger = new Logger(traceId);
logger.log(
`Request: ${method} ${url}, Header: ${JSON.stringify(request.headers)}`,
);
logger.log(
`Request: ${method} ${url}, Body: ${JSON.stringify(request.body)}`,
);
const now = Date.now();
return next.handle().pipe(
tap((response) => {
logger.log(
`Response: ${method} ${url} ${Date.now() - now}ms ${JSON.stringify(response)}`,
);
}),
catchError((error) => {
logger.log(
`Response: ${method} ${url} ${Date.now() - now}ms ${error.message}`,
);
throw error;
}),
);
}
}
데코레이터 생성
- traceId.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const TraceId = createParamDecorator(
(data: unknown, ctx: ExecutionContext): string => {
const request = ctx.switchToHttp().getRequest();
return request.headers['traceId'] || null;
},
);
적용
app.module.ts
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { LoggingInterceptor } from './util/interceptor/Logging.interceptor';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { TraceIdMiddleware } from './util/middleware/traceId.middle';
@Module({
imports: [
UserModule,
],
controllers: [AppController],
providers: [
// 인터셉터를 전역으로 적용한다.
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
AppService,
],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
// 미들웨어를 모든 주소에 적용한다.
consumer.apply(TraceIdMiddleware).forRoutes('*');
}
}
사용
- user.controller.ts
import {
Controller,
Post,
Body,
Logger,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { ResponseUserDto } from './dto/response-user.dto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post('create')
async create(
@TraceId() traceId: string,
@Body() createUserDto: CreateUserDto,
): Promise<ResponseUserDto> {
Logger.log('traceId: ' + traceId);
return this.userService.create(createUserDto);
}
}