ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Core - 로그 추적기(V2 파라미터로 동기화 개발)
    FrameWork/Spring&Spring-boot 2024. 3. 4. 12:52

    V2 파라미터로 동기화 개발

    트랜잭션ID와 메서드 호출의 깊이를 표현하는 가장 단순한 방법은 첫 로그에서 사용한 트랜잭션IDlevel을 다음 로그에 넘겨주면 된다.

    현재 로그의 상태 정보인 트랜잭션IDlevelTraceId에 포함되어 있다. 따라서 TraceId를 다음 로그로 넘겨주면 된다. 이기능을 추가한 HelloTraceV2를 개발해보자.

     

    HelloTraceV2

    @Slf4j
    @Component
    public class HelloTraceV2 {
    
        private static final String START_PREFIX = "-->";
        private static final String COMPLETE_PREFIX = "<--";
        private static final String EX_PREFIX = "<X-";
    
        public TraceStatus begin(String message){
            TraceId traceId = new TraceId();
            Long startTimeMs = System.currentTimeMillis();
            log.info("[{}] {}{}",traceId.getId(),
            addSpace(START_PREFIX,traceId.getLevel()),message);
    
            //1.로그 출력
            //begin에서 TraceId를 담은 TraceStatus가 생성된 다음
            //끝날대 하나의 트랜잭션의 결과에 따라
            // end메서드 혹은 exception 메서드로 반환된다.
            return new TraceStatus(traceId, startTimeMs, message);
    
        }
    
        //V2추가
        public TraceStatus beginSync(TraceId beforeTraceId,String message){
            TraceId traceId = beforeTraceId.createNextId();
            Long startTimeMs = System.currentTimeMillis();
            log.info("[{}] {}{}",traceId.getId(),addSpace(START_PREFIX,traceId.getLevel()),
            message);
            
            return new TraceStatus(traceId, startTimeMs, message);
    
        }
    
        public TraceStatus record(String message,TraceId traceId){
            Long startTimeMs = System.currentTimeMillis();
            log.info("[{}] {}{}",traceId.getId(),addSpace(START_PREFIX,traceId.getLevel()),
            message);
    
            //1.로그 출력
            //begin에서 TraceId를 담은 TraceStatus가 생성된 다음
            //끝날대 하나의 트랜잭션의 결과에 따라
            // end메서드 혹은 exception 메서드로 반환된다.
            return new TraceStatus(traceId, startTimeMs, message);
        }
    
        public void end(TraceStatus status){
            complete(status,null);
        }
    
        public  void exception(TraceStatus status, Exception e){
            complete(status, e);
        }
    
        private void complete(TraceStatus status, Exception e){
            Long stopTimeMs = System.currentTimeMillis();
            long resultTimeMs = stopTimeMs - status.getStartTimeMs();
            TraceId traceId = status.getTraceId();
    
            if(e == null){
                log.info("[{}] {}{} time={}ms", traceId.getId(), addSpace(COMPLETE_PREFIX,
                traceId.getLevel()),status.getMessage(),resultTimeMs);
            } else {
                log.info("[{}] {}{} time={}ms ex={}", traceId.getId(), addSpace(EX_PREFIX, 
                traceId.getLevel()),status.getMessage(),resultTimeMs,e.toString());
            }
    
        }
    
        private static String addSpace(String prefix, int level) {
            StringBuilder sb = new StringBuilder();
            for(int i =0 ; i < level; i++){
                sb.append((i == level - 1) ? "|" + prefix : "|  ");
            }
            return sb.toString();
        }
    }

     

    HelloTraceV2는 기존 코드인 HelloTraceV2와 같고, beginSync(..)가 추가 되었다.

    //V2추가
        public TraceStatus beginSync(TraceId beforeTraceId,String message){
            TraceId traceId = beforeTraceId.createNextId();
            Long startTimeMs = System.currentTimeMillis();
            log.info("[{}] {}{}",traceId.getId(),addSpace(START_PREFIX,traceId.getLevel()),message);
    
            return new TraceStatus(traceId, startTimeMs, message);
        }

     

    beginSync(..)

    • 기존 TraceId에서 createNextId()를 통해 다음 ID를 구현한다.
    • createNextId()의 TraceId 생성 로직은 다음과 같다.
      • 트랜잭션ID는기존과 같이 유지한다.
      • 깊이를 표현하는 Level은 하나 증가한다.(0 → 1)

     

    테스트 코드를 통해 잘 동작하는지 확인해보자.

     

    HelloTraceV2Test

    class HelloTraceV2Test {
    
        @Test
        void begin_end(){
            HelloTraceV2 trace = new HelloTraceV2();
            TraceStatus status1 = trace.begin("hello");
            TraceStatus status2 = trace.beginSync(status1.getTraceId(),"hello2");
            trace.end(status2);
            trace.end(status1);
        }
    
        @Test
        void begin_exception(){
            HelloTraceV2 trace = new HelloTraceV2();
            TraceStatus status1 = trace.begin("hello");
            TraceStatus status2 = trace.beginSync(status1.getTraceId(),"hello2");
            trace.exception(status2,new IllegalStateException());
            trace.exception(status1 ,new IllegalStateException());
        }
    
    }

     

    처음에는 begin(...)을 사용하고, 이후에는 beginSync(..)를 사용하면 된다. beginSync()를 호출할 때 직전 로그의 traceId 정보를 넘겨주어야 한다.

     

    실행 로그를 보면 같은 트랜잭션ID를 유지하고 level을 통해 메서드 호출의 깊이를 표현하는 것을 확인할 수 있다. 

     


    로그 추적기 V2 - 적용

    메서드 호출의 깊이를 표현하고, HTTP 요청도 구분해보자.

    이렇게 하려면 처음 로그를 남기는 OrderContorller.createOrder()에서 로그를 남길 때 어떤 깊이와 어떤 트랜잭션 ID를 사용했는지 다음 차례인 OrderService.orderItem()에서 로그를 남기는 시점에 알아야한다.

    결국 현재 로그의 상태 정보인 트랜잭션IDlevel이 다음으로 전달되어야 한다.

    이 정보는 TraceStatus.traceId에 담겨있다. 따라서 traceId를 컨트롤러에서 서비스를 호출할 때 넘겨주면 된다. 

     

    traceId를 넘기도록 V2 전체 코드를 수정하자

     

    OrderControllerV2

    @RestController
    @RequiredArgsConstructor
    public class OrderControllerV2 {
        private final OrderServiceV2 orderService;
        private final HelloTraceV2 trace;
    
        @GetMapping("/v2/order")
        public String createOrder(String itemId){
            TraceStatus status = null;
            try {
                status = trace.begin("OrderController.createOrder()");
                orderService.orderItem(status.getTraceId(),itemId);
                trace.end(status);
                return "ok";
            }catch (Exception e){
                trace.exception(status,e);
                throw e; //예외를 꼭 다시 던저주어야 한다.
            }
    
        }
    
    }
    • TraceStatus status = trace.begin() 에서 반환 받은 TraceStatus에는 트랜잭션IDlevel 정보가 있는 TraceId가 있다.
    • orderService.orderItem()을 호출할 때 TraceId를 파라미터로 전달한다.
    • TraceId를 파라미터로 전달하기 위해 OrderServiceV2.orderItem()의 파라미터에 TraceId를 추가해야 한다.

     

    OrderServiceV2

    @Service
    @RequiredArgsConstructor
    public class OrderServiceV2 {
    
        private final OrderRepositoryV2 orderRepository;
        private final HelloTraceV2 trace;
    
        public void orderItem(TraceId traceId, String itemId){
    
            TraceStatus status = null;
            try {
                status = trace.beginSync(traceId,"OrderService.orderItem()");
                orderRepository.save(status.getTraceId(),itemId);
                trace.end(status);
            }catch (Exception e){
                trace.exception(status,e);
                throw e;
            }
        }
    }

     

    • orderItem()은 파라미터로 전달 받은 traceId를 사용해서 trace.beginSync()를 실행한다.
    • beginSync()는 내부에에서 다음 traceId 인스턴스를 생성하면서 트랜잭션ID는 유지하고 level은 하나 증가시킨다.
    • beginSync()가 반환한 새로운 TraceStatus를 orderRepository.save()를 호출하면서 파라미터로 전달한다.
    • TraceId()를 파라미터로 전달하기 위해 orderReposioty.save()의 파라미터에 TraceId를 추가해야 한다.

     

    실행 로그를 보면 같은 HTTP 요청에 대해서 트랜잭션ID가 유지되고, level도 잘 표현되는 것을 확인할 수 있다.

     

    남은 문제

    • HTTP 요청을 구분하고 깊이 표현하기 위해서 TraceId 동기화가 필요하다.
    • TraceId의 동기화를 위헤서 관련 메서드의 모든 파라미터를 수정해야 한다.
      • 만약 인터페이스가 있다면 인터페이스까지 모두 고쳐야 하는 상황이다.
    • 로그를 처음 시작할 때는 begin()을 호출하고, 처음이 아닐때는 beginSync()를 호출해야 한다.
      • 만약에 컨트롤러를 통해서 서비스를 호출하는 것이 아니라. 다른곳에서 서비스부터 호출하는 상황이라면 파라미터로 넘길 TraceId가 없다.

     

    HTTP 요청을 구분하고 깊이를 표현하기 위해서 TraceId를 파라미터로 넘기는 것 말고 다른 대안은 없는 걸까?

     

    [출처 - 스프링 핵심원리-고급편, 저 김영한]

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8

     

    스프링 핵심 원리 - 고급편 강의 - 인프런

    스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기 📢 수강

    www.inflearn.com

     

     

    댓글

Designed by Tistory.