잘못된 에러 핸들링과 로그에 관하여
로그레벨에 관하여
개발을 진행하다보면 로그를 테스트나 서비스 상태나 장애 파악을 위해 많이 쓰게 됩니다. 그래서 단계에 따른 레벨을 사용하게 됩니다. log4j 기준으로 RACE, DEBUG,INFO, WARN, ERROR, FATAL 으로 구분을 지어 사용합니다.
개발을 하다보면 debug, info, warn, error를 이용하게 되는 것 같습니다. 오늘은 지금 것 개발하면서 로그 레벨중 제일 애매하다 느낀 warn과 error 대해서 말하려고합니다. (아직 개발에 대한 지식과 실제 사용자들이 이용할때 발생하는 로그에 대해 지식의 부재를 배포를 통해 많이 느끼게되었습니다.)
누구가에는 처리도중에 발생한 문제라면 Error 일 수 있고 또한 예측된 결과이기 때문에 warn으로 할 수 있습니다. 그러다 보니 코드를 작성하는 사람에 따라서 로그레벨일 달라지게 되어서 팀원들과 warn은 간단한오류 조금 값이 문제가 있으면 error로 하자는 의견으로 모두들 try catch를 통해 여러 Exception를 받다보니 로그기준이 애매해졌습니다. 그러다 try와 에러 핸들링 문제가 발생하며 로그에 대한 정의가 필요하다 느끼게 되었습니다.
try와 에러 핸들링 문제의 시작
try {
....
} catch (IOException e) {
throw new SpaceIOException(MILEAGE_IO);
} catch (NullPointerException e) {
log.warn("[MileageVerifyService]Mileage Not found!, {}", e.getMessage());
return Collections.emptyList();
}
어떠한 비즈니스 로직을 진행할때 try
, catch
를 통해 하나의 실행규칙과 예외에 대한 행위를 만들었습니다. 처음 예외가 한두개 있을때는 크게 문제는 없었습니다. 그러나 예외가 많아지고 throw e
와같이 다시 넘기는 경우도많아 졌습니다. 그로인해 점점 코드가 복잡해지니 코드의 가독성도 떨어지고 문제 발생 지점 추적도 어려워졌습니다.
이러한 문제를 해결하기 위해 try
, catch
은 해당 비즈니스로직에서 처리해야할 경우만 사용하고, 간단한 오류는 @ExceptionHandler
어노테이션은 통해 코드 전체에서 핸들링 할 수 있도록 만들었습니다.
우리만의 Exception
public class SpaceException extends Exception {
private final BodyCode bodyCode;
public SpaceException(BodyCode bodyCode) {
this.bodyCode = bodyCode;
}
public SpaceException(String message, BodyCode bodyCode) {
super(message);
this.bodyCode = bodyCode;
}
public SpaceException(Throwable cause, BodyCode bodyCode) {
super(cause);
this.bodyCode = bodyCode;
}
public SpaceException(String message, Throwable cause, BodyCode bodyCode) {
super(message, cause);
this.bodyCode = bodyCode;
}
}
class SpaceExceptionHandler {
@ExceptionHandler(SpaceException.class)
public ResponseEntity<ResBodyModel> handleException(SpaceException e) {
log.error("[SpaceException]Exception Message = {}, class = {}", e.getMessage(), e.getClass());
return SpaceResponse.toResponse(e.getBodyCode(), (int) e.getBodyCode().getStatus());
}
}
우리만의 Exception을 만들어 구현하여 이곳 저것에서 예외를 발생시킬 수 있고 로그 작성코드도 간략하게 생략할 수 있어 조금 코드의 가독성이 기존보다 향상시킬 수 있었지만 이것은 큰 실수로 변하게 되었습니다.
잘못된 핸들링으로 발생한 문제
그러나 여기서 행복한 마무리면 좋겠지만, 오히려 Handler는 우리를 더욱 힘들게 만들게 되버렸습니다. 서비스중에 에러가 발생하면 Slack 채널을 통해 알림을 받도록 만들어 두었더니 이곳 저곳 비즈니스 로직에서 throw
로 인해 슬랙에 Error
로 도배가 되버렸습니다. 분명 정말 Error
로 인해 알림이 있지만, 핸들링을 위해 발생시킨 throw
이 warn
인데도 불구하고 error
잡히는 문제가 발생해버렸습니다. 다시 try
, catch
를 작성해서 묶거나 다시 리팩토링을 진행한다..? 이것 또한 기존처럼 코드 가독성을 저하시킬 수 있다고 판단하였습니다. 그렇다면 현재 코드에서 개선할 수있는 건 개선하고 수정할 부분을 수정하자는 결론을 통해SpaceException와 Exception의 두개의 역할을 분리하기로 했습니다.
class SpaceExceptionHandler {
....
@ExceptionHandler(SpaceException.class)
public ResponseEntity<ResBodyModel> handleException(SpaceException e) {
log.warn("[SpaceException]Exception Message = {}, class = {}", e.getMessage(), e.getClass());
return SpaceResponse.toResponse(e.getBodyCode(), (int) e.getBodyCode().getStatus());
}
....
}
class GlobalExceptionHandler {
....
@ExceptionHandler(Exception.class)
public ResponseEntity<ResBodyModel> handleException(Exception e) {
log.error("[Exception]Exception Message = {}, class = {}", e.getMessage(), e.getClass());
return SpaceResponse.toResponse(GlobalErrorCode.SERVER_ERROR, 500);
}
....
}
대부분의 에러 SpaceException으로 만들어진 것은 warn
이거나 예측 되는 문제들이 대부분이고 경고성인 경우가 많기 때문에 에러 레벨을 warn으로 지정하였고, Exception 같은 경우 전혀 우리가 예측하지 못하는 오류들 즉 발생해서는 안되는 문제들을 핸들링하고 error 레벨로 지정해두었습니다.
에러에 대한 지식
개발단계에서는 Slack에 알림뜨는 횟수도 낮고 큰 문제가 발생하는 것이 없으니 모르고 있었으나, 서비스를 직접 배포하고하니 여러 사용자에 따른 이벤트로인해 Slack 알림을 일시적으로 꺼둬야하는 문제였습니다. 아마도 이것은 경험과 초보 개발자들이라면 충분히 발생할 수 있는 문제라 생각합니다.
특히 로그 레벨 사용에 있어서 팀원과 사용에 대한 정확한 정의가 있지 않고, 각자 알고있는 개념을 통해 사용하니 이러한 문제도 발생한 것 같습니다.
댓글
이 글 공유하기
다른 글
-
패턴을 조합하여 코드를 줄이고 가독성을 개선하자!
패턴을 조합하여 코드를 줄이고 가독성을 개선하자!
2024.07.08 -
Python에서 Java로 마이그레이션하게 된 이유
Python에서 Java로 마이그레이션하게 된 이유
2024.06.23