앞에서 만든 로그추적기는 트랜잭션 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
를 애플리케이션에 직접 적용해보겠다.