로깅의 정의와 구현.
로깅은 애플리케이션에서 발생한 이벤트나 요청을 기록하는 작업을 말한다.
path, 에러, 실행 시간 등 요청 및 응답 정보를 기록함으로써 시스템의 동작을 추적하고, 버그가 발생했을 때 빠르게 원인을 파악하는데에 주로 사용된다.
이 프로젝트에서는 미들웨어 레이어에 로깅을 구현했다.
사용자가 요청을 보내면, 요청의 메서드, URL, 헤더, 본문, 파라미터, 쿼리 등을 DB에 저장하고, 응답이 완료되면 응답 본문과 상태 코드, 요청 처리 시간을 업데이트한다.
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
constructor(
private readonly prisma: PrismaService,
private readonly configService: ConfigService,
private readonly jwtService: JwtService,
) {}
async use(req: Request, res: Response, next: NextFunction) {
const { method, originalUrl: url, headers, body, params, query } = req;
const requestedAt = new Date();
const logId = randomUUID();
const userId = this.getBearerTokenUserId(headers);
// 로그 생성 (요청 시점)
await this.prisma.log
.create({
data: {
id: logId,
user_id: userId ?? null,
method,
url,
headers: JSON.stringify(headers),
body: body ? JSON.stringify(body) : null,
param: params ? JSON.stringify(params) : null,
query: query ? JSON.stringify(query) : null,
ttl: null,
created_at: requestedAt,
},
})
.catch(console.error);
const originalSend = res.send;
// send 메서드 오버라이드
res.send = (responseBody: any): Response => {
const statusCode = res.statusCode;
const duration = Date.now() - requestedAt.getTime();
try {
// 응답 후 로그 업데이트
this.prisma.log
.update({
data: {
response: responseBody ?? null,
ttl: duration,
error: statusCode >= 400,
},
where: { id: logId },
})
.catch(console.error); // 에러가 발생해도 무시하고 진행
} catch (error) {
console.error('Error while updating log:', error);
}
// 응답 전송
return originalSend.call(res, responseBody);
};
next();
}
/**
* 헤더 x-user에서 id를 인증해 반환한다.
* 토큰 정보가 없거나 verify에 실패한 경우에도 로직에 문제는 없기에 null 반환
*
* @param headers 요청 헤더
*/
private getBearerTokenUserId(headers: IncomingHttpHeaders): string | null {
const token = headers['x-user'] as string;
if (token) {
try {
const secret = this.configService.get<string>('JWT_SECRET_USER');
const decoded = this.jwtService.verify(token, { secret }) as Guard.UserResponse;
const userId = decoded.id;
return userId;
} catch (error) {
return null;
}
}
return null;
}
}
@Module({
imports: [
JwtModule.register({ global: true }),
ConfigModule.forRoot({
isGlobal: true,
}),
PrismaModule,
AuthModule,
. . .
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}