티스토리 뷰

기록하게 된 이유

외부에 공개되어있는 API를 운영하면서 간단한 방법으로 업무를 매우 효율적으로 처리한 경험을 회고로 정리해본다.

MSA 에서 어떻게 트레이싱이 이뤄지는지 조금 더 깊게 알게 된 좋은 계기 되었다.

 

이슈 사항

외부에서 사용할 수 있게 공개한 API를 운영을 하다가 보면 다양한 문제를 겪게 된다.

실제 로직 상 이슈가 있어서 수정이 필요한 경우도 있지만, 클라이언트 단에서 잘못 요청을 줬거나 API 스펙에 대한 이해도가 떨어지는 상황에서 문의를 하는 등 문제 자체는 복잡하지 않은 경우가 대부분이다.

 

하지만 운영자 입장에서는 상황 파악에 필요한 정보들은 문의 내용 내 파편화 되어있고, 이를 바탕으로 해당 로그를 찾기엔 생각보다 어렵다.

- 문의 내용 내 이슈가 발생했던 정확한 시간이나 어떤 응답을 받았는지 내용 없이 문의를 하는 경우가 많고

- 정보가 있더라도 1초 내 동일 API로 수십 개의 같은 요청 데이터가 있는 경우도 많으며

- 같은 요청이더라도 MSA 환경에선 전후 과정이 다양하게 발생하기 때문이다.

 

이렇게 정확한 상황 진단을 위해선 실제 이슈가 발생했던 시스템 로그를 찾아내는 것이 가장 중요한 일인데, 이 과정이 항상 매우 오래 걸렸다. 보통 로그 찾는데만 10분 ~ 15분 정도 걸렸다.

 

이 문제를 기존에 사용하고 있던 분산 트레이싱 라이브러리로 간단하게 해결한 사례를 적어본다.

 

해결책

MSA 환경이기 때문에 트레이싱 수단으로 Spring Cloud SleuthZipkin을 사용하고 있다.

https://docs.spring.io/spring-cloud-sleuth/docs/2.2.x-SNAPSHOT/reference/html/

 

Spring Cloud Sleuth

Spring Cloud Sleuth automatically instruments all your Spring applications, so you should not have to do anything to activate it. The instrumentation is added by using a variety of technologies according to the stack that is available. For example, for a s

docs.spring.io

https://github.com/openzipkin/zipkin

 

GitHub - openzipkin/zipkin: Zipkin is a distributed tracing system

Zipkin is a distributed tracing system. Contribute to openzipkin/zipkin development by creating an account on GitHub.

github.com

 

핵심인 Spring Cloud Sleuth에 대해 간단하게 정리하자면:

MSA 환경에서는 하나의 요청이 여러 서비스 간을 오가며 처리되기 때문에 어느 서비스에서 얼마나 걸렸는지, 어디에서 오류가 발생했는지 파악하는 것은 어려운 일이다.

Spring Cloud Sleuth는 Spring Boot 기반 애플리케이션에서 Trace-ID 및 Span-ID를 자동으로 생성 및 전달하여 요청 흐름을 추적할 수 있도록 도와주는 라이브러리다.

  • Trace-ID를 자동으로 생성하여 모든 요청을 추적
  • 요청이 여러 서비스로 전달될 때 Trace-ID를 유지
  • Span을 생성하여 서비스 내 개별 작업 시간 측정
  • 로그에 Trace-ID 및 Span-ID 자동 포함
  • Zipkin, Jaeger 등과 연동하여 시각적 분석 가능

여기서 이 Trace-id가 바로 모든 서비스에 걸쳐 해당 트레이스임을 특정해주는 식별자이기 때문에, 이 값만 알면, 요청 인입부터 마지막 응답까지 모든 과정을 알 수가 있다.

 

이미 사용하고 있는 이 trace-id를 응답 필드에 포함하기만 한다면 클라이언트 문의 내용 파악에 필요한 시간을 획기적으로 줄일 수가 있다.

클라이언트가 문의를 할 때 기존에 기재하던 문의 방식에 문제가 있는 response의 trace-id만 알려주면 바로 찾을 수 있기 때문이다.

 

샘플 코드

프로젝트 의존성에 spring-cloud-sleuth가 추가되어있다고 가정하면,

 

응답객체

public class ApiResponse<T> {
    private String traceId;
    private T data;

    public ApiResponse(String traceId, T data) {
        this.traceId = traceId;
        this.data = data;
    }

    public String getTraceId() {
        return traceId;
    }

    public T getData() {
        return data;
    }
}

 

모든 응답에 trace-id 붙여주는 aop

import brave.Tracer;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TraceIdResponseAspect {

    private final Tracer tracer;

    public TraceIdResponseAspect(Tracer tracer) {
        this.tracer = tracer;
    }

    @Around("execution(* com.yourpackage.controller..*(..))")
    public Object wrapWithTraceId(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();

        // 현재 요청의 Trace ID 가져오기
        String traceId = tracer.currentSpan().context().traceIdString();

        // 기존 응답이 ResponseEntity인 경우
        if (result instanceof ResponseEntity<?> responseEntity) {
            Object body = responseEntity.getBody();
            return ResponseEntity.status(responseEntity.getStatusCode())
                    .body(new ApiResponse<>(traceId, body));
        }

        // 일반 객체 반환 시
        return new ApiResponse<>(traceId, result);
    }
}

 

응답 형태

{
  "traceId": "abc123efg456",
  "data": {
    "name": "John Doe",
    "age": 30
  }
}

 

이렇게 하면 모든 API 응답에 자동으로 traceId가 추가되면서, 시스템 로그를 쉽게 추적할 수 있게 되었다. 🚀

실제로 평균 15분정도 헤매던 시간을 바로 시작부터 로그를 찾을 수 있게 되면서 1분 이내로 항상 로그를 볼 수 있게 되었다.