Contents

1. 레이어별 로깅 자동화 시스템

2. 마주한 한계

3. 로깅 기준 재정립

4. 결론


1. 레이어별 로깅 자동화 시스템

이전 프로젝트에서는 AOP를 통해 컨트롤러 단의 요청/응답만 로깅했지만, 복잡한 서비스 흐름에서는 그것만으로 부족함을 느꼈다. 특히 비즈니스 로직과 데이터 접근 계층에서의 동작 흐름을 명확하게 파악하고 싶었고, 이에 따라 모든 계층에 대해 계층별로 구조화된 로깅 시스템을 설계했다.

1.1. AOP 기반 구조 설계

Spring AOP의 @Around 어노테이션과 포인트컷을 이용해 Controller, Service, Repository 각 계층에 대해 공통적으로 실행되는 로깅 로직을 구성하였다.
각 계층에서 발생하는 메서드 호출은 LogExecutionHandler를 거쳐 LogTrace로 전달되며, 여기서 실행 시간 측정, 예외 처리, 트랜잭션 흐름 로그를 담당한다.

이전 프로젝트에서는 컨트롤러의 진입 지점만 로깅했지만, 실무에서는 로직 흐름을 더 깊이 파악할 필요가 있었다. 이를 보완하기 위해 각 계층을 모두 로깅 대상으로 포함시켰고, 호출의 깊이를 로그만으로도 식별할 수 있도록 |-->, |<--, |<X- 와 같은 시각적 구분 기호를 로그 접두어에 도입하였다.

  • |-->: 메서드 진입 시점 (호출 시작)
  • |<--: 메서드 정상 종료 (호출 완료 및 소요 시간)
  • |<X-: 예외 발생 종료

아래의 실제 로그 예시는 이러한 구조가 어떻게 흐름을 시각화하는지 잘 보여준다:

1.1.1. 정상 흐름

메서드 호출의 계층 구조가 들여쓰기를 통해 표현되어 있으며, |-->|<-- 접두어를 통해 진입과 정상 종료 시점을 명확하게 보여준다.

1.1.2. 예외 발생

예외가 발생한 경우에는 |<X- 접두어와 함께 예외 메시지가 출력되며, 호출 흐름의 어느 지점에서 문제가 발생했는지를 로그만으로 파악할 수 있다.

이처럼 로그 메시지의 시각적 구조화를 통해, 디버깅 시 로그를 단순히 “읽는 것”이 아니라 “트리 형태로 따라가는 것”처럼 느껴지도록 설계했다.


1.2. 계층별 로그 레벨 구분

서비스 구조상 각 계층의 책임이 다르기 때문에, 로깅의 목적도 달라져야 한다. 이에 따라 각 계층의 특성에 맞는 로그 레벨을 분리 적용하였다:

  • Controller: INFO — 외부 요청의 진입 및 응답 흐름을 기록
  • Service: DEBUG — 비즈니스 로직 흐름을 상세히 기록
  • Repository: TRACE — 쿼리 및 데이터 처리 단계의 세부 정보 기록

이렇게 로그 레벨을 계층별로 구분함으로써, 운영 환경에서는 불필요한 로그를 억제하면서도, 디버깅 시점에는 필요한 깊이만큼의 로그를 유연하게 확보할 수 있도록 설계했다.

또한 각 로그는 TraceId와 함께 들여쓰기를 활용해, 로그만 보고도 어떤 계층에서 어떤 처리가 이어지고 있는지 한눈에 파악할 수 있도록 시각적 구조화도 적용하였다.

  • INFO 레벨 로그로 Controller 계층의 진입이 가장 상단에 위치하고,
  • 이어지는 DEBUG, TRACE 로그들이 들여쓰기 되어 있어 Service → Repository 순의 흐름이 계층적으로 표현된다.

이러한 구성 덕분에 로그만으로도 전체 요청 처리의 트리 구조와 계층 이동 흐름을 직관적으로 파악할 수 있으며, 문제 발생 시 어느 계층에서의 처리가 병목이었는지 빠르게 추적할 수 있다.


1.3. 마스킹 및 로그 제외 처리

실제 서비스 환경에서는 비밀번호, 토큰과 같은 민감 정보나, 이미지/파일 데이터처럼 로그에 남기기 부적절한 값이 파라미터나 필드에 포함될 수 있다. 이러한 값들이 그대로 로그에 기록되면 보안과 성능 모두에 문제가 될 수 있다. 이를 해결하기 위해 어노테이션 기반의 마스킹 및 제외 처리 기능을 설계하였다.

  • @Sensitive: 해당 필드는 ****로 마스킹 처리
  • @ExcludeFromLogging: 해당 필드는 로그 출력 대상에서 제외되어 <excluded>로 표시
  • 접근 오류 발생 시 <error>로 처리하여 문제를 표시

이 기능은 MaskingUtils 유틸리티 클래스에서 리플렉션을 활용하여 동적으로 적용되며, 모든 객체 파라미터와 필드에 공통적으로 반영된다.

1.3.1. 민감 정보 마스킹

위 예시에서는 비밀번호 필드에 @Sensitive 어노테이션을 적용하여, 실제 값이 아닌 ****로 출력되도록 처리하였다. 이로써 로그를 확인할 때도 보안성을 유지할 수 있다.

1.3.2. 로그 제외 처리

로그에 포함되면 지나치게 길어지거나 불필요한 데이터(예: 이미지 Base64 값)는 @ExcludeFromLogging 어노테이션을 적용하여 <excluded>로 출력된다. 이를 통해 로그는 가독성을 유지하면서도 필요한 정보만 제공할 수 있다.

마스킹과 제외 처리를 통해 로그는 보안성과 가독성을 함께 충족하는 형태로 개선되었다.


2. 마주한 한계

레이어별 로깅 자동화 시스템을 도입한 이후에도, 실제 서비스 운영 과정에서는 몇 가지 뚜렷한 한계가 드러났다.
구조적으로는 일관된 로깅 체계를 갖추었지만, 실제 디버깅 관점에서는 필요한 정보를 충분히 제공하지 못한 경우가 많았다.

2.1. 파라미터/리턴값 미출력

현재 구조에서는 메서드 실행 흐름과 예외 발생 시점을 확인할 수 있었지만, 메서드의 입력값과 결과값이 상세하게 기록되지 않았다.
특히 private 메서드의 경우 AOP 적용 대상이 아니었기 때문에, 파라미터나 리턴값이 로그에 전혀 남지 않았다.

  • 비즈니스 로직 내부에서 어떤 값이 흘러갔는지 확인하기 어려움
  • 정상 동작이라 판단했던 영역에서도 실제로는 오류가 발생했지만, 입력·출력 값이 남아있지 않아 원인 파악이 지연됨

2.2. 디버깅 시 정보 부족

예외 상황에서는 log.warn, log.error 수준으로만 로깅을 남겼는데, 이 방식은 “문제가 터진 순간”만 기록할 뿐, 그 전후의 문맥을 제공하지 못했다.

그 결과,

  • 에러 로그만으로는 정상 로직에서 어디까지 진행되었는지 추적하기 힘들었고,
  • 원인을 파악하기 위해서는 결국 추가적인 로그 작업을 새로 작성해야 했다.

즉, 설계 당시에는 “자동화된 레이어별 로깅만으로 충분하다”고 판단했지만, 실제 운영에서는 오류를 빠르게 분석하기 위해 더 세밀한 로그가 필요함을 절실히 깨달았다.

동화된 로깅이 흐름 추적에는 유용했지만, 실제 디버깅 상황에서는 필요한 정보가 부족해 문제 해결 속도가 느려지는 한계를 드러냈다.

특히 파라미터와 리턴값 로깅의 부재, private 메서드 미포함, 오류 발생 전후의 맥락 부족이 주요 한계로 드러났다.


3. 로깅 기준 재정립

앞서의 경험을 통해, 자동화된 레이어별 로깅만으로는 실제 디버깅 상황에 충분하지 않다는 점을 깨달았다. 따라서 운영 과정에서 더 실용적인 로깅 기준을 새롭게 정립하게 되었다. 핵심은 “AOP 로깅은 흐름 파악용, 명시적 로깅은 디버깅용” 이라는 역할 분리와, “빠르고 가독성 있는 로그 작성” 이다.

3.1. AOP와 명시적 로깅의 역할 분리

AOP 로깅

  • 공통적으로 필요한 흐름 추적, 실행 시간 측정, 계층 간 이동을 담당
  • 트랜잭션 단위로 호출 트리를 파악하는 데 유용

명시적 로깅

  • 파라미터 값, 리턴 결과, 비즈니스 상태 값은 반드시 직접 로깅
  • 디버깅 시 필요한 정보는 개발자가 판단해서 코드 내에 명시적으로 작성
  • 예:
log.debug("User signup request: userId={}, email={}", userId, email);
log.debug("Created user result: {}", createdUser);

이렇게 역할을 분리함으로써, 자동화는 공통적인 틀을 제공하고, 명시적 로깅은 실제 문제 해결에 필요한 맥락을 보완한다.


3.2. 빠른 기록과 가독성 중심의 메시지 구성

초기에는 모든 로그 메시지를 상수화하여 일관성을 유지하려 했으나, 실제로는 “빠르게 작성하고 바로 확인할 수 있는 로그” 가 훨씬 더 중요했다.

  • 상수화 대신 직접 문자열 작성
    • 로깅의 목적은 “코드 관리”가 아니라 “상황 파악”
    • 따라서 Messages.LOG_XXX 같은 상수보다, 개발자가 바로 이해할 수 있는 직접 작성 메시지가 더 실용적이었다.
  • 가독성 중심 구성
    • "userId={}, status={}" 와 같이 짧고 직관적인 포맷 활용
    • 상황을 빠르게 파악할 수 있도록 필요한 값만 선택적으로 출력

이러한 기준 전환을 통해 로그는 단순한 기록을 넘어, 문제를 신속히 인지하고 원인을 추적할 수 있는 도구로 자리잡게 되었다.


4. 결론

레이어별 자동화 로깅 시스템을 설계하고 운영하면서, 단순히 로그를 남기는 것과 실제로 디버깅에 도움이 되는 로그를 남기는 것은 전혀 다르다는 사실을 깨달을 수 있었다.

  • AOP 기반 자동화 로깅은 흐름 파악호출 트리 확인에 효과적이었다.
  • 그러나 실제 운영에서는 파라미터와 결과 값, 예외 발생 전후의 맥락이 더 중요한 정보임을 알게 되었다.
  • 이에 따라 **“AOP는 공통 추적용, 명시적 로깅은 디버깅용”**이라는 역할 분리를 기준으로 삼게 되었고,
  • 로그 메시지는 상수화보다 빠른 작성과 직관적인 표현을 우선시하는 방식으로 바꾸었다.

이 과정을 통해, 로그는 단순한 기록물이 아니라 문제 상황을 재현하고, 원인을 신속히 파악하게 해주는 핵심 도구로 자리매김했다. 앞으로도 로깅은 “많이 남기는 것”보다, 필요한 순간에 필요한 정보를 정확히 제공하는 것을 목표로 다듬어갈 것이다.