studybook
  • Introduction
  • 실무 하며 깨닫는 부분 정리
    • 옵션에 대해서
    • 코드 작성의 순서
    • 자바 프로그램에 문제가 생겼다면
    • 장애 대처법
  • Logstash, Beats 정리
  • Zookeeper 정리
  • Message Queue 정리
    • RabbitMQ 삽질
  • Java 관련 정리
    • Java Primitive Wrapper class
    • Java NIO
    • Java8 Double colon operator
    • Effective Java
      • 4장
      • 5장
      • 6장 - Enum, Annotation
      • 7장 - Method
      • 8장 - 프로그래밍 일반
      • 9장 - Exception
    • Java8 Lambda expression
    • JDBC
    • Linux에서 WatchService 이상동작
  • Spring 관련 정리
    • Spring Bean init, destroy 순서
    • Spring Async Controller
    • Spring Executable jar 웹 개발 및 배포
    • Spring Boot Font 배포 에러
    • Spring AOP
      • Spring AOP로 모든 Request 로그 남기기
    • Spring Cache
    • Spring Cloud
      • Consul로 spring 설정 관리하기
    • Spring Test
      • Spring Test DirtiesContext
      • Spring Test MockBean, SpyBean
      • Spring Test Dynamic @Scheduled
    • Spring JDBC
    • Spring Validation
    • Spring Transaction Management
      • Spring with JTA 삽질
    • Spring에서 효율적으로 Static resource 관리하기
    • Zuul을 사용해서 Spring Reverse proxy 만들기
    • Spring Security
    • 스프링 어노테이션이 안 먹힐 때 의심해볼만한 것
    • Spring Data
    • Spring Webflux
      • Tobi 강연
  • 코드 리팩토링
    • 한번에 하나씩
  • 지속적 통합 (CI)
    • Jenkins pipeline 삽질기
  • Log Aggregator 정리
    • Flume 테스트
    • Fluentd 테스트
  • Web Socket 정리
  • Akka
    • Actor 모델
    • Supervision
  • IE 8 대응 정리
  • 함수형 프로그래밍
    • 모나드
  • Netty
    • Netty 기본 예제
    • Netty 주요 특징
    • Netty 부트스트랩
    • Netty 채널 파이프라인, 코덱
    • Netty 이벤트 모델
    • Netty 바이트 버퍼
  • 스칼라 관련 정리
    • Maven으로 컴파일하기
    • Scala def 괄호 여부의 차이
    • 스칼라 function, method 차이점
    • ScalaTest와 Spring 연동하기
    • Programming in Scala
  • J2S 컨퍼런스
  • Android
    • 테스트
    • NDK
  • DDOS
  • HTTP
  • HttpClient
  • Container
    • Image 개요
    • cri-o
    • kata containers
    • Open Container Initiative Image
    • Buildkit
  • Github pages
  • Static Website
  • Webhook
  • Service Discovery Tools
    • Etcd
    • Eureka
    • Consul
      • ACL
    • 비교
  • React
    • JSX
    • React Element
    • Components, Props
    • State, Lifecycle
    • Handling Event
    • Flux
  • Vagrant
    • SSH 접속
  • Linux
    • Systemd
    • Alternatives
  • Messaging protocols
    • XMPP
    • AMQP
  • Windows
    • Windows10 내장 우분투에 ssh 클라이언트로 접속하기
    • Windows10 Hyper-V와 Virtual Box가 충돌을 일으켰을 때
    • Hyper-V 기반 docker에서 Shared Drives 설정 실패할 때
    • 윈도우 개발환경 설정
    • Docker desktop 없이 docker 환경 세팅하기
    • UWP 앱을 항상 관리자권한으로 실행하는 바로가기 만들기
  • Spring camp 2017
    • Project Reactive
    • 이벤트 소싱
    • CQRS
  • Spring webflux
  • 리액티브 프로그래밍
  • Linux Settings
    • 홈서버 백업 및 복구기
    • 홈서버 트러블슈팅
  • Kubernetes
    • k3s 설치 및 삽질
    • pod resources
    • Argo workflow
    • 트러블 슈팅
      • Kubernetes namespace의 phase가 Terminating에서 멈춰있을 때
    • 쿠버네티스 마스터
    • Knative
    • Knative Pipeline
    • Aggrerated API server
    • Accessing the API
      • Authenticating
  • Sonarqube
  • HTTP/2
  • Go
    • Go Module
    • Go dependency injection
    • Go Error handling
    • Go in Action
      • 3장 패키지
      • 4장 배열, 슬라이스, 맵
      • 5장 GO의 타입 시스템
      • 6장 동시성
      • 7장 동시성 패턴
      • 8장 표준 라이브러리
      • 9장 테스트와 벤치마킹
    • Go Channel 사용법
  • Cloud Native
Powered by GitBook
On this page
  • 사용법
  • 1. Dependency 추가
  • 2. Annotation 붙이기
  • 3. @Aspect 클래스 만들기
  1. Spring 관련 정리
  2. Spring AOP

Spring AOP로 모든 Request 로그 남기기

처음에는 Controller 메소드 하나하나에 Logger로 로그 찍다가 언제까지 이 짓을 반복할건지 너무 귀찮아졌다. 그래서 그 다음으로 Interceptor 써가지고 로그 찍다가, 이래저래 관리하기 귀찮기도 하고 무엇보다 하나하나의 요청이 얼마나 시간을 소요하는지 찍어볼까했더니 좀 불편하더라. 뭐 더 좋은게 없을까 싶어 잠깐 고민하다가 전에 문서만 봐두고 써보진 않은 Spring AOP가 번뜩 떠올라서 써보게 되었다.

설정도 간편하고 원래 로직과 불필요한 로깅 관련 코드를 완전히 추출해낼 수 있었으며, 원하는 대로 정확히 동작하는 부분이 마음에 들었다. 물론 request 로그를 남기는건 interceptor으로도 충분하지만 웹 요청이 아니라 특정 서비스 등에 대한 로그를 남기는건 AOP로밖에 안 되기도 할 것이고..

사용법

전혀 어렵지 않다. 내 프로젝트는 새로 만든 프로젝트라 스프링 부트 버전 1.5.3.RELEASE 를 사용하고 있다.

1. Dependency 추가

두 가지 방법이 있다. starter을 쓰는 방법과 일일히 하나하나 추가해주는 방법. 스프링 부트를 쓰고 있다면 당연히 starter을 쓰는게 편하다.

gradle

dependencies {
  // 생략
  compile('org.springframework.boot:spring-boot-starter-aop')
}

// 내지는 하나하나 추가해도 된다.
dependencies {
  // 생략
  compile("org.springframework:spring-aop:${springAopVersion}")
  compile("org.aspectj:aspectjweaver:${aspectjweaverVersion}")
}

2. Annotation 붙이기

정확히는 @EnabledAspectJAutoProxy 라는 어노테이션을 앱의 어느 @Configuration 클래스에 붙여야된다. 나는 스프링 부트를 쓸 때 보통 이런 설정 어노테이션들은 나중에 찾기 쉽게 메인에다가 몰아넣는 편이다.

package com.navercorp.npush2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableConfigurationProperties
@EnableAspectJAutoProxy
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

3. @Aspect 클래스 만들기

AOP를 적용하기 위해서는 @Aspect 어노테이션을 붙인 클래스를 하나 만들고 Bean으로 추가해주면 된다. 나는 다음과 같은 코드를 작성하였다.

package com.navercorp.npush2.aop;

import java.util.Map;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.google.common.base.Joiner;

@Component // 1
@Aspect // 2
public class RequestLoggingAspect {
  private static final Logger logger = LoggerFactory.getLogger(RequestLoggingAspect.class);

  private String paramMapToString(Map<String, String[]> paramMap) {
    return paramMap.entrySet().stream()
        .map(entry -> String.format("%s -> (%s)",
            entry.getKey(), Joiner.on(",").join(entry.getValue())))
        .collect(Collectors.joining(", "));
  }

  @Pointcut("within(com.navercorp.npush2.controller..*)") // 3
  public void onRequest() {}

  @Around("com.navercorp.npush2.aop.RequestLoggingAspect.onRequest()") // 4
  public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
    HttpServletRequest request = // 5
        ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

    Map<String, String[]> paramMap = request.getParameterMap();
    String params = "";
    if (paramMap.isEmpty() == false) {
      params = " [" + paramMapToString(paramMap) + "]";
    }

    long start = System.currentTimeMillis();
    try {
      return pjp.proceed(pjp.getArgs()); // 6
    } finally {
      long end = System.currentTimeMillis();
      logger.debug("Request: {} {}{} < {} ({}ms)", request.getMethod(), request.getRequestURI(),
          params, request.getRemoteHost(), end - start);
    }
  }
}
  1. Bean으로 등록하기위해 @Configuration 클래스에서 직접 등록할 수도 있겠지만 나는 그냥 @Component로 등록하는 것을 선호한다.

  2. 이 어노테이션을 붙여야 동작한다.

  3. 혹시 어떻게 HttpServletRequest 를 받아와야할지 모르겠다면 이 부분을 그대로 사용하면 된다.

  4. Around advice는 메소드의 실행 전, 후 접근 및 리턴 값까지 바꿀 수 있는 가장 강력한 advice인데, 이 코드는 실질적으로 Controller의 메소드가 실행되는 부분이다.

PreviousSpring AOPNextSpring Cache

Last updated 7 years ago

Pointcut을 등록하는 부분. 사용할 수 있는 Pointcut 타입들은 여기를 참고하시라. 현재 이 코드에서는 com.navercorp.npush2.controller 패키지 아래에 있는 모든 public 메소드들을 포함하도록 만들었다. (Spring AOP도 프록시 기반으로 동작하다보니까 public이 아닌 다른 메소드들은 인식하지 않는다.) 또 여기서 중요한 것은 @Pointcut 은 어디까지나 적용 범위를 잡는 개념이므로 method body에 뭘 넣든 의미가 없다는 것이다. 빈 채로 두도록 하자.

Advice를 등록하는 부분. 사용할 수 있는 Advice 타입들은 역시 문서를 참고하자. advice에다가 Pointcut을 등록하는 방법엔 이미 정의된 Pointcut 메소드를 가르키게 하는 방법과 inline으로 바로 pointcut을 작성해서 하는 방법 두가지가 있는데 뭐 입맛대로 골라쓰면 될 듯 하다. 나는 따로 정의해서 등록했다.

https://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-pointcuts-designators
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-advice