Nodejs

 

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를 통해 완성됩니다.

  1. Gateway → User/Order/Payment 서비스 호출 시 traceInterceptor가 Metadata에 client-service를 추가.

  2. 서비스 내부 호출 시 constructMetadata로 trace-id와 호출 정보를 유지.

  3. 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. 정리

  1. gRPC 요청/응답을 로깅하는 GrpcInterceptor 구현

  2. trace-id, 호출자/수신자, 처리 시간 등을 로그로 남기는 방법

  3. Metadata를 이어받아 end-to-end tracing을 완성하는 방법

을 다뤘습니다.

 

앞선 Interceptor 강의와 결합하면 서비스 전반의 호출 흐름을 전체 추적(End-to-End Tracing) 할 수 있어, 성능 분석과 장애 대응에 큰 도움이 됩니다.

 

 

 

 

about author

PHRASE

Level 60  라이트

자신의 육체적 정서적 욕구를 무시하고서는 절대로 총체적인 인격체로서 타인과 건전한 관계를 맺을 수 없다.

댓글 ( 0)

댓글 남기기

작성