4.1 KiB
4.1 KiB
Exception Log 예시
좋은 예시
예시 1. 대표 실패만 ERROR로 남긴다
@RestControllerAdvice
public class ApiExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(ApiExceptionHandler.class);
@ExceptionHandler(ExternalAuthUnavailableException.class)
public ResponseEntity<ApiResult<Void>> handleExternalAuthUnavailable(
ExternalAuthUnavailableException ex,
HttpServletRequest request
) {
log.error("Failed request. requestPath={} method={} errorCode={} actorId={}",
request.getRequestURI(),
request.getMethod(),
ErrorCode.UPSTREAM_AUTH_SERVER_UNAVAILABLE.code(),
request.getAttribute("actorId"),
ex);
ErrorCode errorCode = ErrorCode.UPSTREAM_AUTH_SERVER_UNAVAILABLE;
return ResponseEntity.status(errorCode.httpStatus())
.body(ApiResult.fail(errorCode));
}
}
좋은 이유:
- 대표 ERROR 로그가 한 곳에 모인다
- 메시지와 응답 코드가 분리된다
- 운영 키와 stack trace가 함께 남는다
예시 2. 재시도 중간 실패는 WARN 또는 DEBUG로만 남긴다
try {
return externalAuthClient.issueToken(command);
} catch (SocketTimeoutException ex) {
log.warn("External auth attempt failed. provider={} actorId={} attempt={}",
"keycloak", command.actorId(), attemptNumber);
throw ex;
}
좋은 이유:
- 중간 실패를 곧바로 대표 장애처럼 기록하지 않는다
- 재시도 맥락이 드러난다
예시 3. validation 실패는 ERROR로 과장하지 않는다
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResult<Map<String, String>>> handleValidation(
MethodArgumentNotValidException ex,
HttpServletRequest request
) {
log.info("Rejected invalid request. requestPath={} method={} actorId={}",
request.getRequestURI(),
request.getMethod(),
request.getAttribute("actorId"));
Map<String, String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.collect(Collectors.toUnmodifiableMap(
FieldError::getField,
DefaultMessageSourceResolvable::getDefaultMessage,
(first, second) -> first
));
return ResponseEntity.badRequest()
.body(ApiResult.fail(ErrorCode.REQUEST_VALIDATION_FAILED, errors));
}
좋은 이유:
- 예상 가능한 4xx를 서버 장애처럼 기록하지 않는다
- 필요한 요청 맥락은 남긴다
예시 4. 메시지는 사건 설명 중심으로 쓴다
log.error("Failed external auth request. provider={} requestPath={} actorId={} durationMs={}",
provider, requestPath, actorId, durationMs, ex);
좋은 이유:
- 무엇이 실패했는지 바로 보인다
- 예외 메시지 품질에 로그 제목이 종속되지 않는다
나쁜 예시
예시 1. 같은 예외를 여러 레이어에서 반복 ERROR로 찍는다
log.error("Client failed", ex);
log.error("Service failed", ex);
log.error("Controller failed", ex);
나쁜 이유:
- 한 실패가 여러 번 기록된다
- 검색/알림/집계 품질이 나빠진다
예시 2. 예외 메시지를 그대로 제목으로 쓴다
log.error(ex.getMessage(), ex);
나쁜 이유:
- 사건 맥락이 없다
- 민감정보가 메시지에 섞일 수 있다
- 운영 키가 없다
예시 3. 요청 본문 전체를 예외 로그에 남긴다
log.error("Failed create user request. requestBody={}", requestBody, ex);
나쁜 이유:
- PII/비밀번호/토큰이 유출될 수 있다
- payload 전문 로그는 기본 금지다
예시 4. 예상 가능한 business rejection을 ERROR로 남긴다
log.error("Duplicate email sign-up attempt. email={}", request.email());
나쁜 이유:
- 서버 장애처럼 과장된다
- 개인식별정보 전체값이 그대로 남는다
- 운영 신호를 오염시킨다