[Spring Boot] AOP를 활용한 효과적인 trace 및 로깅 구현 방법

1. 프론트엔드 Tracing

Tracing은 각 요청에 고유 식별자(traceId)를 부여하고, 이를 애플리케이션의 여러 계층과 서비스를 거치며 따라가는 과정입니다. 이는 복잡한 시스템에서 문제 해결과 성능 최적화에 큰 도움이 됩니다.

 

프론트엔드에서의 traceId 생성 이점

  1. 전체 시스템 가시성: 사용자 인터랙션부터 백엔드 처리까지 전 과정을 추적할 수 있습니다.
  2. 일관된 모니터링: 모든 서비스에서 동일한 traceId를 사용하여 요청을 추적합니다.
  3. 세션 단위 분석: 사용자 세션 전체에 걸친 활동을 종합적으로 분석할 수 있습니다.

2. 구현

2.1. React에서 traceId 생성 및 전송

프론트엔드 애플리케이션에서 traceId를 생성하고 API 요청에 포함시킵니다.

// traceId 생성 유틸리티
import { v4 as uuidv4 } from 'uuid';

function createTraceId() {
  return localStorage.getItem('traceId') || localStorage.setItem('traceId', uuidv4());
}

// Axios 설정에 traceId 포함
const axiosInstance = axios.create({
  baseURL: 'http://api.example.com',
  headers: {
    'X-Trace-Id': createTraceId(),
    // 기타 필요한 헤더
  }
});

2.2. Spring Boot에서 traceId처리

백엔드에서는 이 traceId를 캡처하고 로깅에 활용합니다. Spring의 MDC(Mapped Diagnostic Context)를 사용하여 이를 구현할 수 있습니다.

@Component
public class TraceFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String traceId = request.getHeader("X-Trace-Id");
        MDC.put("traceId", traceId != null ? traceId : UUID.randomUUID().toString());
        try {
            filterChain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

 

2.3. AOP를 활용한 로깅 구현

Spring AOP를 사용하여 메서드 실행을 자동으로 로깅합니다.

@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

    @Around("execution(* com.example..*(..))")
    public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        logger.info("시작: {} in {} [traceId={}]", methodName, className, MDC.get("traceId"));
        
        Object result = joinPoint.proceed();
        
        logger.info("종료: {} in {} [traceId={}]", methodName, className, MDC.get("traceId"));
        
        return result;
    }
}

2.4. 로그 설정 최적화

logback.xml 을 수정하여 로그 포맷에 traceId를 포함시킵니다.

<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg [traceId=%X{traceId}]%n</pattern>
    </encoder>
  </appender>

  <root level="INFO">
    <appender-ref ref="CONSOLE" />
  </root>
</configuration>

 

 

3. 분산 환경에서의 추적

복잡한 마이크로서비스 환경에서는 더 강력한 분산 추적 도구가 필요할 수 있습니다. 다음과 같은 옵션을 고려해 볼 수 있습니다.

Jaeger

  • Uber에서 개발한 오픈소스 분산 추적 시스템
  • 마이크로서비스 환경에서의 트랜잭션 모니터링
  • Go로 작성되어 확장성이 뛰어남
  • Cassandra와 Elasticsearch를 주요 스토리지로 지원

Zipkin

  • Twitter에서 개발한 오픈소스 분산 추적 시스템
  • 간단한 설정과 사용이 특징
  • Java기반으로 개발, REST API를 통한 쉬운 데이터 수집
  • 상대적으로 가벼운 구조

OpenTelemetry

  • CNCF(Cloud Native Computing Foundation) 프로젝트
  • 로그, 매트릭, 트레이스 등 다양한 Telemetry 데이터 생성 및 수집
  • 여러 프로그래밍 언어와 백엔드 시스템 지원

Prometheus

  • 매트릭 모니터링에 특화된 오픈소스 도구

Datadog APM

  • 상용 APM도구로, SaaS기반의 모니터링 솔루션

이러한 도구들을 사용해서 복잡한 시스템에서 성능 병목 현상을 식별하고, 서비스 간 의존성을 시각화하며, 전체 시스템의 동작을 더 잘 이해할 수 있습니다.