AOP 활용해서 Request 로그 추적 (Spring)

2025. 5. 23. 18:30·Spring/AOP

들어가며

먼저, AOP를 사용해야겠다라고 생각했던 이유는 Redis 캐싱을 적용해서, 게시글 상세 조회나 전체 목록 조회 등의 내용의 응답 시간을 단축 시켰는데, 실제로 구체적인 Request 로그로 확인해보고 싶었기에 해당 내용에 대해서 공부한 뒤 적용해봤다
(Redis 캐싱은 Caffeine + Redis 을 합친 2-Level 캐시로 구현했는데, 구현한 내용은 다른 포스팅에서 작성해보려한다)


AOP의 개념

먼저 AOP와 해당 기능을 사용했을때 장점에 대해서 얘기해보려 한다

AOP란 ?
관점(Aspect)지향 프로그래밍으로, 관점을 기준으로 다양한 기능을 분리하여 보는 프로그래밍이다.
여기서 관점은, 부가 기능과 그 적용처를 정의하고 합쳐서 모듈로 만든 것을 의미한다

 

즉, 정리하자면 비즈니스 핵심적인 기능은 아닐 수도 있지만, 애플리케이션 코드가 중복되고 강력하게 결합되어 있어 다른 로직과 분리할 수 없는 애플리케이션 로직을 의미하여, 로깅, 보안, 트랜잭션 처리 등등에 사용된다고 이해해주면 될거같다


AOP의 장점

일단 앞서 설명했듯이 나눠져 있는 관심 사항 ? 구현 사항이 하나의 장소로 응집될 수 있어서 중복되는 코드를 줄일 수 있다. 그리고 해당 로직은 심사에 대한 코드만 포함하기 때문에 코드가 깔끔해지고 유지보수가 용이해진다

 

결론적으로 난 AOP가 객체지향적으로 코드를 설계할 수 있도록 도와주는 역할을 한다고 생각했다

 

AOP 관련된 내용도 잠깐 찾아보긴 했는데 엄청 깊고, 내용도 많아서 아래 공식문서를 참고해주면 더 이해가 빠를거같다

https://docs.spring.io/spring-framework/reference/core/aop.html

 

Aspect Oriented Programming with Spring :: Spring Framework

Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enabl

docs.spring.io


본론

위에서도 다뤘듯이, 결론적으로 Spring AOP는 로깅과 같은 작업들을 함께 모와서 처리해줄 수 있다. 난 해당 기능을 활용해서 API Request에 대한 로그를 출력하는 공통 클래스를 만들어보았는데, 이제 해당 내용을 깊게 다루어보겠다


의존성 추가

먼저 아래의 AOP 의존성을 추가해주었다

implementation 'org.springframework.boot:spring-boot-starter-aop'

 

여기서 org.json 패키지는 JDK에도, Spring Boot 스타터에도 기본 포함되어 있지 않아서, 아래 사진처럼 import 오류가 발생한다면

JSONObject import 오류

아래 한줄도 추가해주면 따로 내려받을 수 있다

implementation 'org.json:json:20230227'

LogAspect 구현

이제 기본 세팅은 끝났고 실제로 LogAspect 클래스를 구현한 , 해당 클래스를 메서드 별로 나눠보면서 얘기해보려고 한다


LogAspect 적용할 패키지 설정

@Pointcut("execution(* com.sopt.DaisoMall..service..*(..))")
public void businessMethods() {}

@Pointcut("execution(* com.sopt.DaisoMall..controller..*(..))")
public void controllerMethods() {}

일단 먼저 businessMethods과 controllerMethods 메서드를 구현하여 모든 서비스 계층과 모든 컨트롤러 계층의 요청에 적용을 해주었다. 해당 내용은 정규 표현식이라서, 아래 내용처럼 global에 있는 로직들이나, 특정 로직들은 제외할 수도 있다

!execution(* com.sopt.DaisoMall.global..*(..))"

메서드 소요시간 측정

joinPoint.proceed()는, 비즈니스 메서드의 실행을 제어하는데,
joinPoint.proceed() 호출 전은 비즈니스 메서드 호출 전이고, joinPoint.proceed() 호출 후는 비즈니스 메서드 호출 후 이므로 해당 메서드를 이용해 소요시간을 측정할 수 있다

@Around("businessMethods()")
        public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            try {
                return joinPoint.proceed();
            } finally {
                long elapsed = System.currentTimeMillis() - start;
                log.info("▶ {} executed in {} ms",
                        joinPoint.getSignature(), elapsed);
            }
        }

Controller 진입 시 HTTP 요청 정보 + 핸들러 메서드, 파라미터 로깅

@Around("controllerMethods()")
        public Object logControllerRequest(ProceedingJoinPoint joinPoint) throws Throwable {
            ServletRequestAttributes attrs =
                    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

            if (attrs != null) {
                HttpServletRequest req = attrs.getRequest();
                String uri       = URLDecoder.decode(req.getRequestURI(), StandardCharsets.UTF_8);
                String httpMethod= req.getMethod();
                JSONObject params= extractParams(req);

                log.info("[{}] {}", httpMethod, uri);
                log.info("    handler = {}.{}",
                        joinPoint.getSignature().getDeclaringType().getSimpleName(),
                        joinPoint.getSignature().getName());
                log.info("    params  = {}", params.toString());
            }

            return joinPoint.proceed();
        }

 

요청 URI, 헤더 정보, 쿼리 파라미터 등의 정보는 시그니처에 담겨있지 않은 정보다. 그래서 RequestContextHolder의 getRequestAttributes 메서드를 사용하여 해당 정보들을 가져올 수 있었다

또 URLDecoder.decode()를 이용해서 URI를 UTF-8로 디코딩하여 한글 등을 입력받아 올때, 생길 수 있는 이슈들을 방지했다


쿼리 파라미터를 JSONObject 로 변환

private JSONObject extractParams(HttpServletRequest request) {
    JSONObject json = new JSONObject();
    Enumeration<String> names = request.getParameterNames();
    while (names.hasMoreElements()) {
        String name = names.nextElement();
        String safe = name.replaceAll("\\.", "_");
        json.put(safe, request.getParameter(name));
    }
    return json;
}

마지막으로 이전 메서드에서 HttpServletRequest.getParameterNames로 전달된, 각 파라미터 이름을 키로 호출 결과를 값으로 JSONObject에 담아 반환해주었다

이때 String safe = name.replaceAll("\\.", "_"); 을 통해서 key 에 점(.)이 들어올 경우 언더스코어(_)로 대체해서 발생할 수 있는 예외케이스도 함께 정상적으로 반환될 수 있도록 설정했다


실제 Request 로그 반환 내용

브랜드 검색 API에 대한 Request 로그
소요 시간 -> 94ms
상품 검색 API에 대한 Request 로그
소요 시간 -> 41ms

'Spring > AOP' 카테고리의 다른 글

AOP 기반 Lock 인터페이스 추상화, Redisson 분산락 적용 동시성 처리(Spring)  (1) 2025.06.01
'Spring/AOP' 카테고리의 다른 글
  • AOP 기반 Lock 인터페이스 추상화, Redisson 분산락 적용 동시성 처리(Spring)
huncozyboy
huncozyboy
이지훈
  • huncozyboy
    열정을 기록하기
    huncozyboy
  • 전체
    오늘
    어제
    • 분류 전체보기 (63)
      • Spring (26)
        • JWT (3)
        • 무한 스크롤 (1)
        • 매칭 로직 (2)
        • OAuth (4)
        • 자동화 (1)
        • 캐싱 (1)
        • AOP (2)
        • Swagger (1)
        • S3 (1)
        • CORS (1)
        • Spring Retry (0)
        • Webhook (2)
        • Grapheme Cluster (1)
        • 연관관계 (1)
        • CS 개념 (5)
      • DevOps (13)
        • 스왑 메모리 (1)
        • Blue Green (2)
        • Docker (7)
        • Route 53 (1)
        • 리버스 프록시 (2)
      • AI (2)
        • Claude Code (1)
        • Copilot (1)
      • CS (4)
        • JAVA (4)
      • Github (1)
        • Conflict (1)
      • Python (4)
        • Langchain (3)
        • Crawling (1)
      • 일상 (3)
        • 회고록 (1)
      • 알고리즘 (10)
        • 투포인터 (0)
        • 슬라이딩 윈도우 (0)
        • 정렬 (0)
        • 이분 탐색 (0)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    DevOps
    redis
    java
    스프링
    EC2
    https
    자바
    LangChain
    수도코드
    OAuth
    Spring
    코딩테스트
    프로그래머스
    Docker
    aws
    백준
    코테
    알고리즘
    도커
    JWT
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
huncozyboy
AOP 활용해서 Request 로그 추적 (Spring)
상단으로

티스토리툴바