Spring adv 02 - log ProtoType 적용
ProtoType 적용
@RestController
@RequiredArgsConstructor
public class OrderControllerV1 {
private final OrderServiceV1 orderService;
private final HelloTraceV1 trace;
@GetMapping("/v0/request")
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;
}
}
}
HelloTraceV1은 로그 추적을 담당하는 클래스이며, @Component 애노테이션을 통해 Spring의 빈으로 등록된다. 이로 인해 OrderControllerV1 클래스에서는 HelloTraceV1을 자동으로 주입받을 수 있다. request 메서드는 클라이언트로부터 들어온 요청을 처리하는 컨트롤러 메서드로, trace.begin(“OrderController.request”)로 로그 추적을 시작하고, orderService.orderItem(itemId)로 실제 주문을 처리한다. 주문 처리가 완료되면 trace.end(status)로 로그를 종료하며, 예외가 발생할 경우 trace.exception(status, e)로 예외를 추적한다. 이러한 방식으로 요청의 흐름을 추적함으로써 요청 처리 과정에서 발생할 수 있는 문제를 빠르게 파악하고 디버깅할 수 있다. 그러나 trace.begin()과 trace.end()만으로는 예외 처리가 어렵기 때문에 try-catch 블록을 사용하여 예외를 처리하고, 예외가 발생한 경우에도 적절히 로깅을 남긴다.
troubleShooting
begin() 의 결과 값으로 받은TraceStatus status 값을 end() , exception() 에 넘겨야 한다. 결국try , catch 블록 모두에 이 값을 넘겨야헀다. try 상위에 TraceStatus status 코드를 선언해야 한다. 만약 try 안에서 TraceStatus status 를 선언하면 try 블록안에서만 해당 변수가 유효하기 때문에 catch 블록에 넘길 수 없다. 따라서 컴파일 오류가 발생한다. 따라서 정상 흐름으로 동작하기 위해서는 throw e로 예외를 던져줘야 애플리케이션 흐름에 영향을 주지 않는다.
orderItem2 처럼 메서드를 만들어도 될 것 같은데 왜 굳이 try-catch 구문을 사용했을까?
public void orderItem2(String itemId) {
TraceStatus status = trace.begin("OrderService.orderItem");
trace.exception(status, new IllegalStateException());
orderRepository.save(itemId);
trace.end(status);
}
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;
}
}
결론은 orderItem2 메소드에서 trace.exception(status, new IllegalStateException())를 사용하여 예외를 명시적으로 기록할 수 있지만, 예외 처리와 트레이싱의 역할을 제대로 수행하려면 try-catch 구문을 사용하는 것이 더 적합하다.
예외 발생을 명시적으로 처리하는 역할
trace.exception(status, new IllegalStateException())는 예외가 발생했다는 사실을 추적 시스템에 기록하기 위해 사용되지만, 실제로 예외를 던지거나 처리하는 것이 아니다. 이는 예외를 강제로 발생시키는 것일 뿐 실제 예외 흐름을 관리하는 것과는 다르다. 예를 들어, orderItem2에서 예외를 던지지 않으면, 시스템에서 어떤 예외가 발생했는지 알기 어려워질 수 있다. try-catch 구문을 사용하면 예외를 명확하게 처리하고, 재발생시키거나 다른 방식으로 처리할 수 있다.
추적 상태가 누락될 가능성
trace.end(status)는 정상적으로 메소드가 완료되었을 때 호출되는 코드이다. 하지만 예외가 발생하여 trace.exception(status, e)가 호출되더라도 예외가 처리되지 않으면 trace.end(status)가 호출되지 않고 메소드가 중단될 수 있다. try-catch 구문을 사용하면 예외 발생 시에도 trace.end(status)가 호출되도록 보장할 수 있다.
테스트
http://localhost:8080/v1/request?itemId=hello
HelloTraceV1 덕분에 직접 로그를 하나하나 남기는 것 보다는 편하게 여러가지 로그를 남길 수 있었다. 하지만 로그를 남기기 위한 코드가 생각보다 너무 복잡하다. 왜 모니터링을 쓰는지도 알 것 같다. 또 의존성 주입이 많아지면 의존성 관리가 어려워 질 수 있다. (양방향 의존성이 생길 수도 있고)
Reference
김영한님의 스프링 핵심 원리
나의 뇌