다른 부분은 저번 과제의 연속이라 잘 해결이 되었지만,
package org.example.expert.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.time.LocalDateTime;
@Slf4j
@Aspect
@Component
public class AdminAPILoggingAspect {
private final ObjectMapper objectMapper;
public AdminAPILoggingAspect(ObjectMapper objectMappers) {
this.objectMapper = objectMappers;
}
@Around("execution(* org.example.expert.domain.comment.controller.CommentAdminController.*(..)) || " +
"execution(* org.example.expert.domain.user.controller.UserAdminController.*(..))")
public Object logAdminApiCall(ProceedingJoinPoint joinPoint) throws Throwable {
// 요청 및 응답 정보 로깅
HttpServletRequest request = getHttpServletRequest();
HttpServletResponse response = getHttpServletResponse();
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
// 요청 본문 로깅
String requestBody = new String(wrappedRequest.getContentAsByteArray());
log.info("Admin API Request: Time={}, URL={}, UserId={}, Body={}",
LocalDateTime.now(), request.getRequestURI(), request.getHeader("User-Id"), requestBody);
Object result = joinPoint.proceed(); // 실제 메서드 실행
// 응답 본문 로깅
String responseBody = new String(wrappedResponse.getContentAsByteArray());
log.info("Admin API Response: Time={}, URL={}, UserId={}, Body={}",
LocalDateTime.now(), request.getRequestURI(), request.getHeader("User-Id"), responseBody);
wrappedResponse.copyBodyToResponse(); // 응답 본문을 클라이언트에 전달
return result;
}
private HttpServletRequest getHttpServletRequest() {
// HttpServletRequest 가져오는 로직
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
private HttpServletResponse getHttpServletResponse() {
// HttpServletResponse 가져오는 로직
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
}
현재 짰던 코드를 확인해 보았지만, 정확히는 파악이 안되어서 여러가지 찾아본 결과 나온 코드이다. 하지만 아직 정확하게는 판단이 안서서 하나하나 분석해 보려고 한다.
1. 클래스 정의
@Slf4j
@Aspect
@Component
public class AdminAPILoggingAspect {
- @Slf4j: 로깅을 위해 사용하는 Lombok 애노테이션. log.info, log.error 같은 로깅 메서드를 제공.
- @Aspect: 이 클래스가 AOP 역할을 수행하는 클래스라는 의미.
- @Component: Spring이 이 클래스를 Bean으로 관리
2. 생성자
public AdminAPILoggingAspect(ObjectMapper objectMappers) {
this.objectMapper = objectMappers;
}
- ObjectMapper: JSON 처리를 위한 Jackson 라이브러리 객체야. 요청이나 응답 본문을 JSON으로 직렬화하거나 역직렬화할 때 사용.
3. AOP 핵심: @Around
@Around("execution(* org.example.expert.domain.comment.controller.CommentAdminController.*(..)) || " +
"execution(* org.example.expert.domain.user.controller.UserAdminController.*(..))")
public Object logAdminApiCall(ProceedingJoinPoint joinPoint) throws Throwable {
- @Around: 특정 메서드 실행 전후에 로직을 실행하도록 설정.
- 여기서는 CommentAdminController와 UserAdminController 클래스의 모든 메서드에 AOP를 적용
- ProceedingJoinPoint joinPoint: 실제 대상 메서드를 호출할 수 있는 객체. AOP 적용 메서드 전후로 추가 로직을 실행 가능.
4. 요청 및 응답 객체 가져오기
HttpServletRequest request = getHttpServletRequest();
HttpServletResponse response = getHttpServletResponse();
- 현재 요청과 응답 객체를 가져오는 거야. getHttpServletRequest()와 getHttpServletResponse()는 아래에서 따로 정의돼 있음.
5. 요청 로깅
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
String requestBody = new String(wrappedRequest.getContentAsByteArray());
log.info("Admin API Request: Time={}, URL={}, UserId={}, Body={}",
LocalDateTime.now(), request.getRequestURI(), request.getHeader("User-Id"), requestBody);
- 요청 본문(Body)을 읽기 위해 ContentCachingRequestWrapper를 사용.
- getContentAsByteArray()로 요청 데이터를 가져온 뒤 로그로 출력.
- 로그에 포함된 정보:
- 요청 시간 (LocalDateTime.now()).
- 요청 URL (request.getRequestURI()).
- 요청자 ID (request.getHeader("User-Id")).
- 요청 본문 내용 (requestBody).
6. 대상 메서드 실행
Object result = joinPoint.proceed();
- AOP가 적용된 원래 메서드를 실행.
- 여기선 CommentAdminController나 UserAdminController의 메서드가 실행됨.
7. 응답 로깅
ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
String responseBody = new String(wrappedResponse.getContentAsByteArray());
log.info("Admin API Response: Time={}, URL={}, UserId={}, Body={}",
LocalDateTime.now(), request.getRequestURI(), request.getHeader("User-Id"), responseBody);
- 응답 본문을 로깅. ContentCachingResponseWrapper로 응답 데이터를 읽음.
- 로그에 포함된 정보:
- 응답 시간.
- 요청 URL.
- 요청자 ID.
- 응답 본문 내용.
8. 응답 복원
wrappedResponse.copyBodyToResponse();
- ContentCachingResponseWrapper가 데이터를 읽으면 스트림이 닫히기 때문에, 복원해서 클라이언트에 다시 보냄.
9. 요청/응답 객체 가져오기
private HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
private HttpServletResponse getHttpServletResponse() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
- RequestContextHolder를 사용해서 현재 요청(Request)과 응답(Response) 객체를 가져옴
- AOP는 컨트롤러 메서드 앞뒤에서 동작하니까, 여기서 현재 요청 정보를 얻음
코드 전체 흐름
- 특정 관리자 API가 호출되면, AOP가 작동.
- 요청 정보를 가져와서 로깅.
- 원래 메서드를 실행 (joinPoint.proceed()).
- 응답 정보를 로깅.
- 요청과 응답 모두 클라이언트로 전달.