JaeWon's Devlog
article thumbnail
반응형

Spring 에서 개발을 하면서 예외가 발생되면 @ControllerAdvice 가 선언된 클래스가 동작하여 알맞은 예외타입(RuntimeException, NullPointerException 등)에 메소드가 호출된다.

대부분 프로젝트마다 로깅하는 방법이 정해져있기 때문에, 거의 사용되지는 않겠지만 빠르게 개발하면서 나중에 변경해야지 하고 e.printStackTrace() 로 작성하는 경우가 있다.(작성자 본인이 그렇다...)

 

전체 에러 메세지는 보기는 싫고, 실제 예외가 발생된 부분(소스 위치)만 로깅하고 싶었는데 구글링해보니 똑같이 생각하신 분이 계셔서 참고하게 되었다.)

 

이번 글에서는 e.printStackTrace를 말고 getStackTrace 로 간단한 코드를 통해서 확인해보려 한다.


1. e.printStackTrace() 사용을 지양해야 하는 이유

  1. System.err 로 쓰여지게 되면서 제어하기 힘들다.
  2. System.err 로 쓰여지게 되면서 리소스 비용이 비싼편이다.
  3. Java의 Reflection(리플렉션)을 사용하여 예외를 추적하는 것이라 많은 오버헤드가 발생한다.
  4. 서버에서 메소드(Method) 스택정보를 취합하기 때문에 서버에 부하의 원인이 된다.
  5. 출력이 어디로 향하는지 파악하기 어렵다.(톰캣의 경우 catalina.out 에 남을 수도 있다)
  6. 관리가 어렵다.(보통 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를 이용하여 출력한다.

참고

- https://kim-jong-hyun.tistory.com/112

- https://tgyun615.com/59

반응형
profile

JaeWon's Devlog

@Wonol

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!