어 나 갱수.

[Spring] AOP를 적용한 logging을 해보자 본문

Spring

[Spring] AOP를 적용한 logging을 해보자

김경수 2024. 5. 10. 14:40
728x90

AOP

AOP에 대해 먼저 알아보자면 관전 지향 프로그래밍이라는 뜻으로, 코드를 핵심 기능과 공통 기능으로 나눈 후에 핵심 기능에서 따로 빼놓은 공통 기능을 불러와 적용하는 방법입니다.

로깅 작업

프로젝트를 하면서 로그를 찍는 거는 아주 중요한 역할입니다. 로그는 에러가 난 이유를 찾거나 값을 확인하는데 중요한 역할을 합니다.

위의 코드와 같이 Controller 코드에 로그를 추가할 수 있습니다.

 

하지만 위와 같이 Controller method마다 로그를 찍으면 문제가 생깁니다.

  • 중복된 코드 증가(모든 controller 메서드에 로그를 추가해야 한다.)
  • 실수로 로그를 찍지 않으면 로그가 찍히지 않는다.

그래서 위와 같은 상황에서 좀 더 효율적으로 하려면 핵심기능(회원가입)과 공통기능(로깅 작업)을 분리해서 작업하는 것이 더 효율적이다.

 

Spring AOP Logging

Aspect와 Pointcut 정의

@Aspect
@Component
class HttpLoggingAspect {

    private val log = LoggerFactory.getLogger(this.javaClass.name)

    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
    fun onRequest() {
    }
}
  • @Aspect 어노테이션은 해당 클래스가 AOP의 Aspect임을 선언합니다.
  • @Component 어노테이션은 스프링이 이 클래스의 인스턴스를 Bean으로 관리하게 합니다.
  • @Pointcut 어노테이션은 로깅 대상을 지정합니다. 여기서는 @RestController 어노테이션이 붙은 모든 클래스 내의 메서드를 대상으로 합니다.

Around Adivce 정의 및 로깅

@Around("onRequest()")
@Throws(Throwable::class)
fun logging(proceedingJoinPoint: ProceedingJoinPoint): Any? {
  • @Around 애너테이션은 대상 메소드 실행 전후에 특정 로직을 수행하도록 합니다. 여기서는 onRequest() 포인트컷에 지정된 메소드 실행 전후에 로깅을 수행합니다.
  • ProceedingJoinPoint는 대상 메서드에 대한 정보와 함께 메소드 실행을 제어할 수 있는 기능을 제공합니다.

요청 정보 로깅

val request = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request
val ip = request.remoteAddr
val method = request.method
val uri = request.requestURI
val sessionId = request.requestedSessionId
val params = request.queryString
val contentType = request.contentType
val userAgent = request.getHeader("User-Agent")
val signature: MethodSignature = proceedingJoinPoint.signature as MethodSignature
val className = signature.declaringType.simpleName
val methodName = signature.name
val headerNames = request.headerNames
val headerSet: MutableSet<String> = HashSet()
  • HTTP 요청 객체에서 IP 주소, HTTP 메서드, URI, 세션 ID, 쿼리 스트링, 콘텐츠 타입, 사용자 에이전트 등의 정보를 추출합니다.

응답 정보 로깅

val result = proceedingJoinPoint.proceed()
when (result) {
    is ResponseEntity<*> -> {
        log.info(
            "At {}#{} [Response:{}] IP: {}, Session-ID: {}, Headers: {}, Response: {}, Status-Code: {}, Code: {}",
            className,methodName,method,ip,sessionId,result.headers,result.body,result.statusCode,code
        )
    }

    null -> {
        log.info(
            "At {}#{} [Response: null] IP: {}, Session-ID: {}, Code: {}",
            className,methodName,ip,sessionId,code
        )
    }

    else -> {
        throw RuntimeException("유효하지 않은 Controller 반환 타입입니다.")
    }
}
return result
  • 대상 메서드의 실행 결과에 따라 응답 정보를 로깅합니다. ResponseEntity 타입의 경우, 헤더, 바디, 상태 코드를 로깅합니다.

로깅 

[2024-05-10 14:36:26:29772][http-nio-8080-exec-2] INFO  com.dotori.v2.aop.HttpLoggingAspect - At AuthController#signInEmailAndPassword [Request:POST] IP: 0:0:0:0:0:0:0:1, Session-ID: B73A8C63477A8FCE63D7FBB330F5C5AF, URI: /v2/auth, Params: null, Content-Type: application/json, User-Agent: PostmanRuntime/7.37.3, Headers: [content-length, cookie, postman-token, host, content-type, connection, accept-encoding, user-agent, accept], Parameters: {signInEmailAndPasswordReqDto=SignInEmailAndPasswordReqDto(email=s00000@gsm.hs.kr, password=String1!)}, Code: 019d3976-78b7-4afa-bd1f-f1d255c1b521

 

HTTP 요청을 하면 위와 같은 로깅이 발생합니다. 

 

이번 포스팅에서는 Spring AOP를 활용하여 HTTP 요청과 응답을 효율적으로 로깅하는 방법에 대해 알아보았습니다.
실제 예시를 통해 어떻게 Spring 애플리케이션에서 AOP 기반의 로깅 시스템을 구현할 수 있는지 살펴보았습니다. 감사합니다.

728x90