Spring 에서 개발을 하면서 예외가 발생되면 @ControllerAdvice 가 선언된 클래스가 동작하여 알맞은 예외타입(RuntimeException, NullPointerException 등)에 메소드가 호출된다.
대부분 프로젝트마다 로깅하는 방법이 정해져있기 때문에, 거의 사용되지는 않겠지만 빠르게 개발하면서 나중에 변경해야지 하고 e.printStackTrace() 로 작성하는 경우가 있다.(작성자 본인이 그렇다...)
전체 에러 메세지는 보기는 싫고, 실제 예외가 발생된 부분(소스 위치)만 로깅하고 싶었는데 구글링해보니 똑같이 생각하신 분이 계셔서 참고하게 되었다.)
이번 글에서는 e.printStackTrace를 말고 getStackTrace 로 간단한 코드를 통해서 확인해보려 한다.
1. e.printStackTrace() 사용을 지양해야 하는 이유
- System.err 로 쓰여지게 되면서 제어하기 힘들다.
- System.err 로 쓰여지게 되면서 리소스 비용이 비싼편이다.
- Java의 Reflection(리플렉션)을 사용하여 예외를 추적하는 것이라 많은 오버헤드가 발생한다.
- 서버에서 메소드(Method) 스택정보를 취합하기 때문에 서버에 부하의 원인이 된다.
- 출력이 어디로 향하는지 파악하기 어렵다.(톰캣의 경우 catalina.out 에 남을 수도 있다)
- 관리가 어렵다.(보통 log4j, logback 으로 로그 패턴 및 로그 메세지를 지정하여 사용한다.)
2. e.printStackTrace() 코드
- 간단하게 Controller / Service 사이에서 에러를 발생시키는 코드를 사용하겠습니다.
- /logTest 로 요청이 들어오면 Service 를 호출하고, 1/0(ArithmeticException) 을 하여 예외를 발생시키겠습니다.
- Controller
@RestController
@RequiredArgsConstructor
@Slf4j
public class TestController {
private final LogTestService logTestService;
@GetMapping("/logTest")
public void logTest() {
try {
logTestService.logTest();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 각 프로젝트마다 로깅 위치가 다르겠지만, 예시를 위해 Controller 에서도 try-catch 를 통해서 예외를 출력 시키겠습니다.
- Service
@Service
@Slf4j
public class LogTestService {
public void logTest() {
try {
int num1 = 1;
int num2 = 0;
double result = num1/num2;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
- 결과
- e.printStackTrace 를 사용하게 된다면, 아래와 같이 무수히 많은 줄의 에러로그를 출력하게 됩니다.
- 이러한 경우 불필요한 내용도 많고, 최초 발생지인 Service 에서도 예외를 출력하는데 예외를 Throw 하게 되면서, Controller 에서도 예외를 출력하면서 중복 및 정확한 원인은 알 수 없이 출력이 되고 있습니다.
java.lang.ArithmeticException: / by zero
at com.example.finlife_collection.service.LogTestService.logTest(LogTestService.java:14)
at com.example.finlife_collection.controller.TestController.logTest(TestController.java:29)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
...
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)
...
at java.base/java.lang.Thread.run(Thread.java:834)
java.lang.RuntimeException
at com.example.finlife_collection.service.LogTestService.logTest(LogTestService.java:17)
at com.example.finlife_collection.controller.TestController.logTest(TestController.java:29)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
...
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
...
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
...
at java.base/java.lang.Thread.run(Thread.java:834)
3. e.getStackTrace() 코드
- e.printStackTrace 의 결과를 모두 출력하기 보다는 예외가 발생된 부분만 출력하면 불필요한 로깅이 줄어들고 성능에도 이점이 생길 수 있습니다.
- Throwable 클래스에서 제공하는 getStackTrace() 라는 메소드에서 StackTraceElement 배열타입을 반환하는데 이 메소드 반환값의 인덱스 0 번째가 실제 예외가 발생된 지점입니다.
- e.printStackTrace 대신 e.getStackTrace 를 사용하면서 예외 출력 코드를 조금 변경해보겠습니다.
- Controller
@RestController
@RequiredArgsConstructor
@Slf4j
public class TestController {
private final LogTestService logTestService;
@GetMapping("/logTest")
public void logTest() {
try {
logTestService.logTest();
} catch (Exception e) {
log.error("Exception [Err_Msg]: {}", e.getMessage());
//e.printStackTrace();
}
}
}
- Service
@Service
@Slf4j
public class LogTestService {
public void logTest() {
try {
int num1 = 1;
int num2 = 0;
double result = num1/num2;
} catch (Exception e) {
//e.printStackTrace();
// StackTraceElement[] stackTraceElements = e.getStackTrace();
// stackTraceElements == e.getStackTrace()[0]
log.error("Exception [Err_Location] : {}", e.getStackTrace()[0]);
throw new RuntimeException(e);
}
}
}
- 결과
- Service 에서는 실제 예외가 발생한 곳의 위치만 로깅하고, Controller 에서는 예외가 발생한 원인을 로깅합니다.
- 이렇게 사용함으로써, 조금 더 깔끔한 로깅을 할 수 있는 것 같습니다.
2023-06-28 15:33:58.415 ERROR 8524 --- [nio-8080-exec-1] c.e.f.service.LogTestService : Exception [Err_Location] : com.example.finlife_collection.service.LogTestService.logTest(LogTestService.java:16)
2023-06-28 15:33:58.421 ERROR 8524 --- [nio-8080-exec-1] c.e.f.controller.TestController : Exception [Err_Msg]: java.lang.ArithmeticException: / by zero
Java는 메소드가 호출되면 메소드의 콜스택을 추적하기 위해 StackTrace 라는 곳에 메소드의 콜스택 정보들이 적재되며 예외가 발생되면 마지막 호출이 된 메소드 콜스택 정보를 포함해서 System.err를 이용하여 출력한다.
참고
'BackEnd > Spring' 카테고리의 다른 글
[Spring] @Transactional 사용 시 주의할 점(Feat. 오픈전 발견한 장애) (0) | 2023.08.13 |
---|---|
[Spring] MyBatis 사용 시 resultType 에 InnerClass 사용하기 (2) | 2022.12.10 |
[SpringBoot] CouchBase 연동하기 (0) | 2022.11.27 |
[SpringBoot] @Valid 를 이용한 유효성(Validation) 검사(Feat. @RequestBody, @Validated) (0) | 2022.10.29 |
[Spring] @Scheduled 어노테이션에서 cron 사용 및 정리 (0) | 2022.08.11 |