1. Tracing의 필요성
마이크로서비스 환경에서는 서비스 간 요청이 복잡하게 얽혀 있습니다. 이때 장애가 발생하거나 성능이 저하되면 어느 서비스에서 문제가 시작됐는지 추적하기 어렵습니다. trace-id를 요청마다 부여하고, 이를 모든 서비스 호출에서 유지하면 전체 요청 흐름을 추적할 수 있습니다.
2. GrpcInterceptor 구현
Tracing을 위해 공통 Interceptor(GrpcInterceptor)를 작성합니다. 이 Interceptor는 요청과 응답을 가로채 trace-id, 호출자/수신자 정보, 데이터, 처리 시간을 기록합니다.
import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common'; import { map, Observable } from 'rxjs'; export class GrpcInterceptor implements NestInterceptor { intercept( context: ExecutionContext, next: CallHandler<any>, ): Observable<any> | Promise<Observable<any>> { const data = context.switchToRpc().getData(); const ctx = context.switchToRpc().getContext(); const meta = ctx.getMap(); const targetClass = context.getClass().name; const targetHandler = context.getHandler().name; const traceId = meta['trace-id']; const clientService = meta['client-service']; const clientClass = meta['client-class']; const clientMethod = meta['client-method']; const from = `${clientService}/${clientClass}/${clientMethod}`; const to = `${targetClass}/${targetHandler}`; const requestTimestamp = new Date(); const receivedRequestLog = { type: 'RECEIVED_REQUEST', traceId, from, to, data, timestamp: requestTimestamp.toUTCString(), }; console.log(receivedRequestLog); return next.handle().pipe( map((data) => { const responseTimestamp = new Date(); const responseTime = responseTimestamp.getTime() - requestTimestamp.getTime() + 'ms'; const responselog = { type: 'RECEIVED_RESPONSE', traceId, from, to, data, responseTime, timestamp: responseTimestamp.toUTCString(), }; console.log(responselog); return data; }), ); } }
요청과 응답 모두 trace-id, 호출자(from), 수신자(to), 데이터, 응답 시간을 포함해 로그가 남습니다.
3. AppModule에서 적용
각 마이크로서비스 Controller에 @UseInterceptors(GrpcInterceptor)를 선언하면 됩니다. 예시:
@Controller('order') @OrderMicroservice.OrderServiceControllerMethods() @UseInterceptors(GrpcInterceptor) export class OrderController implements OrderMicroservice.OrderServiceController { constructor(private readonly orderService: OrderService) {} async createOrder( request: OrderMicroservice.CreateOrderRequest, metadata: Metadata, ) { console.log('OrderController createOrder:', request); return this.orderService.createOrder(request, metadata); } }
이제 Order 서비스 요청마다 trace-id가 기록되고 응답 시간까지 추적됩니다.
4. Metadata와 Tracing 흐름
Tracing은 앞선 강의에서 구현한 constructMetadata 유틸리티와 traceInterceptor를 통해 완성됩니다.
Gateway → User/Order/Payment 서비스 호출 시 traceInterceptor가 Metadata에 client-service를 추가.
서비스 내부 호출 시 constructMetadata로 trace-id와 호출 정보를 유지.
Controller에서 GrpcInterceptor가 요청/응답을 로깅.
이 흐름으로 전체 호출 체인을 추적할 수 있습니다.
5. NotificationService 예시
Notification 서비스에서 Order 서비스로 메시지를 보낼 때 Metadata를 이어받아 전달합니다.
this.orderService.deliveryStarted( { id }, constructMetadata( NotificationService.name, 'sendDeliveryStartedMessage', metadata, ), );
이 방식으로 trace-id가 유지되어 전체 호출이 하나의 체인으로 연결됩니다.
6. PaymentService 예시
Payment 서비스에서 결제 성공 후 Notification 서비스로 알림을 보냅니다.
const resp = await lastValueFrom( this.notificationService.sendPaymentNotification( { to, orderId }, constructMetadata(PaymentService.name, 'sendNotification', metadata), ), ); console.log('✅ sendNotification resp : ', resp);
이때도 trace-id가 유지되어 Order → Payment → Notification 호출 전체를 추적할 수 있습니다.
7. 정리
gRPC 요청/응답을 로깅하는 GrpcInterceptor 구현
trace-id, 호출자/수신자, 처리 시간 등을 로그로 남기는 방법
Metadata를 이어받아 end-to-end tracing을 완성하는 방법
을 다뤘습니다.
앞선 Interceptor 강의와 결합하면 서비스 전반의 호출 흐름을 전체 추적(End-to-End Tracing) 할 수 있어, 성능 분석과 장애 대응에 큰 도움이 됩니다.
댓글 ( 0)
댓글 남기기