필드 동기화 - 개발

앞에서 만든 로그추적기는 트랜잭션 Id와 level을 동기화하기 위해 아이디를 파라미터로 넘기도록 구현하는 소요가 있었다.

이 문제를 해결하기 위해 새로운 로그 추적기를 만들어보자.

인터페이스

앞으로 계속 발전시킬 로그추적기의 인터페이스이다.

package hello.advance.trace.logtrace;

import hello.advance.trace.TraceStatus;

public interface LogTrace {
    
    TraceStatus begin(String message);
    
    void end(TraceStatus status);
    
    void exception(TraceStatus status, Exception e);
}

이 인터페이스를 채워넣자.

이전과 달라진건 이전에서는 파라미터로 계속 id를 넘겨줬다면, 이제는 TraceHolder객체를 이용해 trace객체 내부에서 값을 유지하도록 하는 것임.

    private TraceId traceIdHolder; //이 상태에서는 TraceId 동기화, 동시성 이슈가 발생함.

    private void syncTraceId() {
        if (traceIdHolder == null) {
            traceIdHolder = new TraceId();
        } else {
            traceIdHolder = traceIdHolder.createNextId();
        }
    }
    
       private void releaseTraceId() {
        // 트레이스 아이디가 첫번재 레벨이면
        if (traceIdHolder.isFristLevel()) {
            traceIdHolder = null; //초기화함.
        } else {
            //아니라면 레벨을 하나 낮춤.
            traceIdHolder = traceIdHolder.createPreviousId(); //레벨을 하나 낮추고, 다음 레벨로 이동
        }
    }

전체코드는 이렇게 된다.

package hello.advance.trace.logtrace;

import hello.advance.trace.TraceId;
import hello.advance.trace.TraceStatus;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FieldLogTrace implements LogTrace {

    private static final String START_PREFIX = "-->";
    private static final String COMPLETE_PREFIX = "<--";
    private static final String EX_PREFIX = "<X--";

    private TraceId traceIdHolder; //이 상태에서는 TraceId 동기화, 동시성 이슈가 발생함.

    private void syncTraceId() {
        if (traceIdHolder == null) {
            traceIdHolder = new TraceId();
        } else {
            traceIdHolder = traceIdHolder.createNextId();
        }
    }

    @Override
    public TraceStatus begin(String message) {
        syncTraceId();
        TraceId traceId = traceIdHolder;
        Long startTimeMs = System.currentTimeMillis();
        log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message);
        return new TraceStatus(traceId, startTimeMs, message);
    }

    @Override
    public void end(TraceStatus status) {
        complete(status, null);

    }

    @Override
    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.getMeessage(), resultTimeMs);
        } else {
            log.info("[{}] {}{} time={}ms ex={}", traceId.getId(), addSpace(EX_PREFIX, traceId.getLevel()), status.getMeessage(), resultTimeMs, e.toString());
        }

        //레벨을 하나씩 낮추거나, 초기화하는 로직.
        releaseTraceId();
    }

    private void releaseTraceId() {
        // 트레이스 아이디가 첫번재 레벨이면
        if (traceIdHolder.isFristLevel()) {
            traceIdHolder = null; //초기화함.
        } else {
            //아니라면 레벨을 하나 낮춤.
            traceIdHolder = traceIdHolder.createPreviousId(); //레벨을 하나 낮추고, 다음 레벨로 이동
        }
    }

    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();
    }
}

이후 테스트해보면 위의 방식으로 만든 로그추적기는 적용할 때 코드 수정이 많이 필요없다는 것을 알 수 있다.

 @Test
    void begin_end_level2() {
        TraceStatus status1 = trace.begin("hello1");
        TraceStatus status2 = trace.begin("hello2");

        trace.end(status2);
        trace.end(status1);
    }

필드 동기화 - 적용

위에서 만든 FieldLogTrace 를 애플리케이션에 직접 적용해보겠다.