Spring 기반의 백엔드 개발을 하다 보면 다음과 같은 요구사항이 생길 수 있습니다.
- 클라이언트에게는 일정한 응답 포맷을 유지하면서
- 내부적으로는 경고 상황임을 감지해야 하며
- 로그에는
ERROR레벨이 아닌WARN또는INFO수준으로만 기록하고 싶다
이 글에서는 위와 같은 상황에서 예외 처리 방식을 어떻게 구성하면 좋을지 실제 구현 예제를 기반으로 정리하였습니다.
문제 상황
예외가 발생할 때 다음과 같이 @ResponseStatus가 지정되어 있는 커스텀 예외를 사용하고 있었습니다.
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "custom error")
public class CustomException extends RuntimeException {
public CustomException(ErrorCode code, String message) {
super(message);
}
}
서비스 로직에서는 특정 조건에서 이 예외를 던지고 있었습니다:
if (!inputData.isValid()) {
throw new CustomException(ErrorCode.INVALID_INPUT, "Invalid input detected");
}
문제점
@ResponseStatus로 인해 예외 발생 시 자동으로 HTTP 400 응답이 반환됨- Spring 내부적으로
ERROR로그가 남게 됨 - 단순한 경고 상황임에도 과도한 로그 기록이 발생
해결 방안
1. @ResponseStatus 제거
예외 클래스에서 @ResponseStatus 어노테이션을 제거합니다.
- @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "custom error")
public class CustomException extends RuntimeException {
// ...
}
이제 이 예외는 HTTP 상태코드에 자동 매핑되지 않으며, 전역 예외 처리기에서 직접 제어할 수 있습니다.
2. 전역 예외 처리기 구성
@ControllerAdvice를 통해 전역 예외 처리기를 생성하고, 로그 레벨을 warn 또는 info로 지정합니다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomException.class)
public ResponseEntity<?> handleCustomException(CustomException ex) {
// ERROR 로그 대신 WARN으로 기록
log.warn("CustomException 발생: {}", ex.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("code", "INVALID_INPUT");
response.put("message", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
}
이제 예외가 발생해도 로그에는 ERROR가 아닌 WARN 수준으로만 기록됩니다.
3. 서비스 로직에서 예외를 던지지 않고 메시지로만 처리
예외를 실제로 throw하지 않고, 메시지를 수집해 API 응답에 포함시키는 방식으로 전환합니다.
if (!inputData.isValid()) {
Map<String, Object> warningDetails = new HashMap<>();
warningDetails.put("field", "inputData");
warningDetails.put("reason", "Validation failed");
log.warn("입력값 유효성 검사 실패: {}", warningDetails);
warningMessages.add(new WarningMessage(
"INVALID_INPUT",
"Validation failed on inputData",
warningDetails
));
// throw 없이 처리 계속 진행
}
이렇게 하면 클라이언트에게는 경고 메시지를 포함한 정상적인 응답을 보낼 수 있고, 로그에도 ERROR 없이 필요한 정보만 남길 수 있습니다.
정리
| 항목 | 처리 내용 |
|---|---|
| 예외 발생 시 ERROR 로그 제거 | @ResponseStatus 제거 및 전역 예외 처리기에서 로그 레벨 조정 |
| API 응답 포맷 유지 | 예외 대신 경고 메시지를 수집하여 응답에 포함 |
| 서비스 흐름 제어 | throw 없이 흐름을 유지하거나 선택적으로 종료 |
마무리하며
이 글을 작성하게 된 직접적인 계기는, 매주 서비스 운영 리포트를 작성하는 과정에서 발생한 불편함 때문이었습니다. 저희 팀은 주기적으로 서버 로그를 분석해 에러 로그를 취합하고, 그 결과를 정리해 보고하는 업무를 진행하고 있습니다.
그런데 실제 시스템 상에서 큰 문제가 아닌 단순 경고 상황조차도 ERROR 레벨 로그로 남아 있다 보니, 매주 리포트 작성 시 다음과 같은 문제가 발생했습니다.
- 실제로 중요한 오류 로그를 선별하는 데 많은 시간이 소요되고
- 단순 경고임에도 불필요하게 심각한 이슈처럼 분류되며
- 반복적으로 의미 없는 항목을 필터링하는 데 추가적인 인적 리소스가 들어가게 됨
이를 해결하기 위해, 예외 처리를 단순히 throw하는 방식이 아니라, 경고 메시지로 기록하고 흐름을 유지하는 방식으로 리팩토링을 시도하게 되었습니다.
그 결과, API 응답의 일관성은 유지하면서도, 운영 리포트 작성 시 정확하고 효율적인 로그 분류가 가능해졌습니다.
이러한 개선 경험을 공유드리며, 같은 고민을 하시는 분들께 도움이 되었으면 합니다.
'개발 (Development) > Java' 카테고리의 다른 글
| [Java] LinkedHashMap에서 특정 값을 가진 항목 제거하는 방법 (0) | 2025.07.05 |
|---|---|
| [Java/SpringBoot] Spring OAuth2 시스템에서 발생한 Access Token 만료 및 인증 오류 대응 기록 (2) | 2025.06.28 |
| [Java] printStackTrace() 경고 해결 및 로깅 적용하기 (0) | 2025.06.28 |
| [Java/Spring Boot] AWS 환경에서 DB 직접 접근이 어려운 경우: Java 애플리케이션에서 SQL 파일을 실행하여 데이터 삽입하기 (2) | 2025.05.18 |
| [Java] Java jar 파일에서 리소스 파일 경로 사용하는 법 (0) | 2025.05.18 |