[ThatzFit] AOP로 로깅 일원화

2025. 9. 2. 09:41·프로덕트 문제 해결 기록

배경

서비스 전반에 반복되는 로깅 코드가 많아 가독성과 유지보수성이 떨어졌습니다. 공통 관심사인 로깅을 AOP로 분리해 DRY 원칙(Don't Repeat Yourself)을 지키는 것을 목표로 했습니다. 

목표

  • 서비스/컨트롤러/레포지토리 단의 공통 로깅을 AOP로 분리
  • 메서드 실행 시간 로깅으로 병목 구간 식별
  • 예외 로깅 표준화
  • 기존 서비스 코드에서 불필요한 시작/완료 로그 제거

설계

  • ServiceLoggingAspect: 서비스 레이어의 메서드 시작/완료 자동 로깅
  • PerformanceLoggingAspect: 서비스/컨트롤러 실행 시간 로깅
  • ExceptionLoggingAspect: 서비스/컨트롤러/레포지토리 예외 로깅

핵심 포인트컷

// 서비스 레이어
execution(* tryonu.api.service..*ServiceImpl.*(..))
// 컨트롤러 레이어
execution(* tryonu.api.controller..*Controller.*(..))
// 레포지토리 예외 로깅
execution(* tryonu.api.repository..*Repository*.*(..))

구현 포인트

1) ServiceLoggingAspect 인자 포매팅 성능 개선

인자 문자열 결합에서 reduce 대신 Collectors.joining을 사용해 효율 개선.

private String formatArguments(Object[] args) {
    return Arrays.stream(args)
        .map(arg -> arg == null ? "null" : {
            String s = arg.toString();
            String name = arg.getClass().getSimpleName();
            if (name.contains("MultipartFile")) return "MultipartFile[...]";
            if (s.length() > 100) return name + "[" + s.substring(0, 100) + "...]";
            return s;
        })
        .collect(Collectors.joining(", "));
}

잠시 reduce와 Collectors.joining을 비교해보겠습니다.

기존 방식: reduce와 문자열 접합(+)

// 예시: reduce를 사용한 비효율적인 방식
.reduce((s1, s2) -> s1 + ", " + s2)
.orElse("");

이 방식의 가장 큰 문제는 Java에서 문자열(String) 객체가 불변(Immutable)하다는 특성 때문에 발생합니다.

  • s1 + ", " + s2 연산이 실행될 때마다 기존의 s1, s2 문자열은 그대로 둔 채, 완전히 새로운 String 객체가 메모리에 생성됩니다.
  • 예를 들어, 인자가 10개라면 이 결합 과정에서 최소 9개의 불필요한 중간 결과 문자열 객체가 생성되고 버려집니다.
  • 인자의 개수가 많아질수록 이 비효율은 기하급수적으로 커져서 CPU와 메모리에 부담을 줄 수 있습니다. 로깅은 매우 자주 일어나는 작업이니까요!! 

🚀 개선된 방식: Collectors.joining

.collect(Collectors.joining(", "));

Collectors.joining은 내부적으로 StringBuilder 또는 StringJoiner 와 같은 가변(Mutable) 객체를 사용합니다.

  • StringBuilder는 단 하나의 객체만 생성한 뒤, 기존 객체에 계속해서 문자열을 **추가(append)**합니다.
  • 스트림의 모든 요소를 다 순회한 후, 마지막에 딱 한 번 .toString()을 호출하여 최종 결과 String 객체를 생성합니다.
  • 결과적으로, 결합 과정에서 불필요한 중간 객체가 전혀 생성되지 않아 매우 빠르고 메모리 효율적입니다.

2) 성능 로깅 기준 일원화

  • 서비스: 100ms 이상만 경고 로그
  • 컨트롤러: 응답시간 단계별 로깅 (상수로 관리)

3) 예외 로깅의 역할 명확화

  • 클래스 설명을 실제 동작(각 레이어)과 일치하도록 정제
  • 비즈니스 예외(CustomException)와 시스템 예외를 구분해서 로깅

적용 효과

  • 서비스 코드에서 반복 로깅 제거 → 핵심 로직만 남아 가독성↑
  • 실행 시간 자동 수집 → 느린 경로 즉시 파악

마무리

AOP로 로깅을 중앙화해 코드가 한층 가벼워졌습니다. 특히 인자 포매팅의 Collectors.joining 도입으로 작은 비용까지 줄였다는 점이 만족스럽네요. 다음은 임계치/로그 레벨을 환경별로 조정 가능한 설정화까지 확장할 계획입니다.

참고

  • 스프링 AOP 총정리: 개념/프록시 기반 AOP/@AOP
 

[Spring] 스프링 AOP (Spring AOP) 총정리 : 개념, 프록시 기반 AOP, @AOP

| 스프링 AOP ( Aspect Oriented Programming ) AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로

engkimbs.tistory.com

 

'프로덕트 문제 해결 기록' 카테고리의 다른 글

WebClient와 CachingFilter 사용 시 InputStream 재사용 오류 해결  (0) 2025.09.03
[ThatzFit] Spring Data JPA 비관적 락 리팩토링: 문제 해결 기록  (1) 2025.09.01
[ThatzFit] 트랜잭션 최적화: 쓰기 경계 분리  (1) 2025.09.01
[ThatzFit] API 에러 모니터링: 체계적인 슬랙(Slack) 알림 시스템 구축기  (0) 2025.08.14
[WIMI] Spring에서 "No acceptable representation" 오류  (0) 2025.03.28
'프로덕트 문제 해결 기록' 카테고리의 다른 글
  • WebClient와 CachingFilter 사용 시 InputStream 재사용 오류 해결
  • [ThatzFit] Spring Data JPA 비관적 락 리팩토링: 문제 해결 기록
  • [ThatzFit] 트랜잭션 최적화: 쓰기 경계 분리
  • [ThatzFit] API 에러 모니터링: 체계적인 슬랙(Slack) 알림 시스템 구축기
givemethatsewon
givemethatsewon
  • givemethatsewon
    프로덕트 중심 개발자
    givemethatsewon
  • 전체
    오늘
    어제
    • 분류 전체보기 (11)
      • 컴퓨터 (4)
        • CS (4)
        • 에러 트러블 슈팅 (0)
        • 알고리즘 (0)
      • 프로덕트 문제 해결 기록 (6)
        • Clody (0)
        • 8fit (0)
        • WIMI (0)
      • etc (1)
        • 경험 (0)
        • 정보 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    udp
    공유메모리
    socket
    rust
    의존성
    tcp
    Jackson
    파이프
    spring
    UML
    인터페이스
    스프링
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
givemethatsewon
[ThatzFit] AOP로 로깅 일원화
상단으로

티스토리툴바