- 목차
- 파라미터로 넘겨주지 않고 메서드 간에 traceId를 공유할 수 있는 방법이 없을까?
[ 로그 추적기 구현 - 필드 사용 ]
public interface LogTrace {
TraceStatus begin(String message);
void end(TraceStatus status);
void exception(TraceStatus status, Exception e);
}
@Slf4j
@Component
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; // 파라미터로 넘기는 게 아닌 필드로 보관한다.
@Override
public TraceStatus begin(String message) {
syncTraceId();
TraceId traceId = traceIdHolder; // syncTraceId()를 수행하고 난 후이기 때문에 값이 설정되어 있다.
// 시간 정보
Long startTimeMs = System.currentTimeMillis();
// 로그 출력
log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message);
return new TraceStatus(traceId, startTimeMs, message);
}
private void syncTraceId(){
if(traceIdHolder == null){ // 최초 호출 시 초기 값 생성
traceIdHolder = new TraceId();
}else { // 직전 로그가 있으면 해당 로그의 traceId를 참고해서 동기화하고, level도 하나 증가
traceIdHolder = traceIdHolder.createNextId();
}
}
@Override
public void end(TraceStatus status){ // 로그를 정상 종료
// 파라미터로 시작 로그의 상태(TraceStatus)를 전달 받는다.
complete(status, null);
}
@Override
public void exception(TraceStatus status, Exception e){ // 로그를 예외 상황으로 종료
// 파라미터로 시작 로그의 상태(TraceStatus)와 발생 예외 정보를 전달 받는다.
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());
}
releaseTraceId(); // 메서드를 호출이 끝나고 level을 하나 감소해주는 메서드
}
private void releaseTraceId() {
if(traceIdHolder.isFirstLevel()){ // 만약 최초 호출 level == 0에 도달하면 내부에서 관리하는 traceId를 제거
traceIdHolder = null; // destroy
} else { // level == 0 아니라면 해당 로그의 traceId를 참고해서 동기화하고, level도 하나 감소시킨다.
traceIdHolder = traceIdHolder.createPreviousId();
}
}
private static String addSpace(String prefix, int level) {...}
- 단순하고, 유연하며 변경이 용이하게 만들기 위해 **역할(인터페이스)**과 **구현(클래스)**으로 구분
- private void syncTraceId()
- TraceId를 새로 만들거나 앞선 로그의 TraceId를 참고해서 동기화하고, level 도 증가한다.
- 결과를 traceIdHolder field에 보관한다.
- private void releaseTraceId()
- 메서드를 추가로 호출할 때는 level 이 하나 증가해야 하지만, 메서드 호출이 끝나면 level 이 하나 감소해야 한다.
- releaseTraceId() 는 level을 하나 감소한다.
- 만약 최초 호출( level==0 )이면 내부에서 관리하는 traceId를 제거한다.
[ 로그 추적기 적용 - 필드 사용 ]
- 필드에 값을 보관하게 되면서 TraceId를 각 계층에서 파라미터 추가 없이 공유할 수 있게 되었다.
Controller
@Controller
@RequiredArgsConstructor
public class OrderControllerV3 {
private final OrderServiceV3 orderService;
private final LogTrace trace; // 인터베이스(역할) 주입
@GetMapping("/v3/request")
@ResponseBody
public String request(String itemId){
TraceStatus status = null;
try {
status = trace.begin("OrderController.request()");
orderService.orderItem(itemId);
trace.end(status);
return "ok";
}catch (Exception e){
trace.exception(status, e);
throw e;
}
}
}
Service
@Service
@RequiredArgsConstructor
public class OrderServiceV3 {
private final OrderRepositoryV3 orderRepository;
private final LogTrace trace;
public void orderItem(String itemId){
TraceStatus status = null;
try {
status = trace.begin("OrderService.orderItem()");
orderRepository.save(itemId);
trace.end(status);
}catch (Exception e){
trace.exception(status, e);
throw e;
}
}
}
Repository
@Repository
@RequiredArgsConstructor
public class OrderRepositoryV3 {
private final LogTrace trace;
public void save(String itemId){
TraceStatus status = null;
try {
status = trace.begin("OrderRepository.save()");
//저장 로직
if(itemId.equals("ex")){ // 만약 상품의 아이디가 "ex" 이면 문제 발생
throw new IllegalStateException("예외 발생!");
}
sleep(1000); // 상품 저장 소요 시간 지정(1초)
trace.end(status);
}catch (Exception e){
trace.exception(status, e);
throw e;
}
}
private void sleep(int millis){
try {
Thread.sleep(millis);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
- 하지만 필드에 값을 공유함으로써 동시성 문제가 발생하였다.
