2025.09.29 - [SpringBoot] - [SpringBoot] AOP 기반 로깅 시스템 구축하기
[SpringBoot] AOP 기반 로깅 시스템 구축하기
Spring Boot로 프로젝트를 진행하다 보면, 로그 관리가 점점 중요해진다.처음에는 @Slf4j 로 콘솔에만 로그를 찍어도 충분하지만,실제 출시를 준비 중이라면, 다음과 같은 생각이 들게 될 것이다.API
jyitdevelopment.tistory.com
지난 포스팅에서 프로젝트 진행 중에 AOP 기반 로깅 시스템을 구축하는 과정의 내용을 담았다.
구현한 코드를 기반으로 같이 프로젝트에 멘토해주시는 현직자분이 리뷰를 해주셨는데,
만약 로그를 저장하는 과정에서 예외가 발생하면, 비즈니스 로직(컨트롤러나 서비스)이 정상적으로 수행되었더라도
로그 예외로 인해 전체 트랜잭션이 롤백될 수 있다.
이러한 상황을 방지해야 한다는 피드백을 받았다.
나는 코드를 짜면서, 이 부분은 생각을 못했는데, 확실히 다르다는 생각을 했다...(더욱더 정진을 해야만...)
그런데 한가지 의문점이 생겼다. 어차피 트랜잭션 분리인데, 로그의 예외가 비즈니스 로직에 영향을 줄까?
하지만 직접 테스트를 돌려보니, 트랜잭션이 분리되어 있다고 해서 예외 전파까지 차단되는 것은 아니었다.
"자바에서 기본적으로 예외가 발생했을때 처리해주지 않으면 콜스택을 하나씩 제거하면서 최초 호출한곳까지 예외가 전파된다."
라고 아래 블로그를 통해 알게 되었다.
https://woodcock.tistory.com/40
Transactional REQUIRES_NEW에 대한 오해
서론예전에 함께 스터디를 했던 스터디원이 트랜잭션에 관한 블로그 글을 공유하면서, 흥미로운 내용이라고 소개했다.해당 글에서는 기존에 내가 알고있던 사실이 틀리다라고 얘기하는 내용이
woodcock.tistory.com
스프링의 @Transaction은 예외 흐름까지 분리하는 기능은 아니다.
즉, 로그 트랜잭션이 별도로 생성되어도 그 안에서 발생한 예외가 catch되지 않으면
이 예외는 그대로 상위(Service 혹은 Controller)비즈니스 로직으로 전파된다.
결과적으로 비즈니스 트랜잭션은 커밋될지라도, 요청 전체가 실패로 간주되어 HTTP 500 에러로 응답되는 것이다.
아래가 실제 테스트 내용이다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(Member member, Character action, String message) {
System.out.println("로그 저장 시작");
Log log = Log.builder()
.member(member)
.action(action)
.time(System.currentTimeMillis())
.message(message)
.build();
logRepository.save(log);
// 일부러 예외 발생시켜보기
throw new RuntimeException("예외 발생");
}
비즈니스 로직은 문제없이 잘 들어가기 때문에 아래 컨트롤러 성공 로그는 잘 저장이 되지만, 예외 로그는 저장이 되지 않는 것을 확인할 수 있었다. 더불어 실제, 투두의 데이터도 들어오지 않았다.



실제 터미널 로그도 이와 같이 발생한다.
로그 저장 시작
Hibernate:
insert into log (action, created_at, member_id, message, time, updated_at)
values (?, ?, ?, ?, ?, ?)
java.lang.RuntimeException: 예외 발생
at com.offnal.shifterz.log.service.LogService.saveLog(LogService.java:56)
ERROR ... GlobalExceptionHandler : [Exception] 예외 발생
...
WARN ... ExceptionHandlerExceptionResolver : Resolved [java.lang.RuntimeException: 예외 발생]
- Hibernate INSERT 수행 시도
- RuntimeException 발생
- @Transactional이 자동으로 롤백 수행
- Commit 없이 종료
- GlobalExceptionHandler로 제어가 넘어감
따라서 트랜잭션을 분리했다 하더라도, 로그 트랜잭션 내에서 발생한 예외를 적절히 try-catch로 처리하지 않으면
예외는 상위 호출 스택으로 전파되어 컨트롤러 단까지 영향을 미친다.
다시 말하면, 트랜잭션의 분리와 예외 전파는 별개의 개념이며, “비즈니스 로직의 성공 여부”와 “로그 저장 실패”를 완전히 독립적으로 다루고 싶다면,로그 저장 로직 내부에서 반드시 예외를 처리(try-catch)해야 한다.
이를 적용하여, 다음과 같이 처리할 수 있다:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(Member member, Character action, String message) {
try {
System.out.println("로그 저장 시작");
Log log = Log.builder()
.member(member)
.action(action)
.time(System.currentTimeMillis())
.message(message)
.build();
logRepository.save(log);
} catch (Exception e) {
System.out.println("로그 저장 실패: " + e.getMessage());
}
}



이렇게 하면 http 200 응답과 함꼐 todo 데이터가 잘 들어왔음을 확인할 수 있,

터미널 로그에는 실제 로그 저장 예외가 발생함을 알 수 있다.
참고
https://kdh0518.tistory.com/57
[Transactional] propagation=requires_new 옵션으로, 자식 트랜잭션의 예외에 상관없이 부모 트랜잭션 커밋
Untitled 서비스에서는 회원가입시 '기본 유저 정보' 외에 '반려 동물' 정보, '관심사' 정보를 입력받아서 처리해준다.회원가입을 구현하셨던, 개발을 꽤 잘하는 김 모 개발자님은(지금은 취뽀하셔
kdh0518.tistory.com
https://techblog.woowahan.com/2606/
'SpringBoot' 카테고리의 다른 글
| [SpringBoot] cicd/blue-green/docker 빌드-배포까지 성공했지만 새 코드가 반영되지 않는 문제 (0) | 2025.11.29 |
|---|---|
| [SpringBoot] JPA / Hibernate / Spring Data JPA (0) | 2025.10.13 |
| [SpringBoot] AOP 기반 로깅 시스템 구축하기 (0) | 2025.09.29 |
| [SpringBoot] 금액권을 Toss 토스 결제 후, QR코드로 생성하기 (0) | 2025.09.01 |
| [SpringBoot] TOSS/토스 결제 API 연동하기 (5) | 2025.07.30 |