이전 이야기
https://record47584.tistory.com/94
Spring 프로젝트 되살리기 8 - 인가(Authorization) 인터셉터 구현
이전 이야기https://record47584.tistory.com/93 Spring 프로젝트 되살리기 7 - 리팩토링 4 (record, 정적 팩토리 메서드)이전 이야기https://record47584.tistory.com/91 Spring 프로젝트 되살리기 6 - 유닛 테스트 코드 고
record47584.tistory.com
시나리오 9
관리자 api 기능을 요청했을 때, 요청 시간과 요청자 id, request/response body 값을 로깅해달라는 요청이 들어왔다.
AOP를 이용해 이를 구현해보자
(AOP에 대한 개념과 구현 방법은 아래 글을 참고)
https://record47584.tistory.com/95
AOP(Aspect-Oriented Programming)의 개념과 구현
AOP(Aspect-Oriented Programming)란? 여러 클래스에 공통으로 필요한 로직(로깅, 트랜잭션, 실행시간 측정 등)을 비즈니스 로직과 분리해서 한 곳에 모아두는 방식만약 모든 api의 실행 시간을 체크해야
record47584.tistory.com
관리자 로깅 AOP 구현
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class AdminAop {
private final TimeUtil timeUtil;
private final ObjectMapper objectMapper;
@Around("execution(* org.example.expert.domain..service..*AdminService.*(..))")
public Object logAdminRequest(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
// 시간
long startTime = System.currentTimeMillis();
// 데이터
Long userId = (Long) request.getAttribute("userId");
String requestUrl = request.getRequestURI();
String requestBody = extractRequestBody(joinPoint.getArgs());
String methodName = joinPoint.getSignature().toShortString();
log.info("[Admin API 요청] userId={}, time={}, url={}, method={}, requestBody={}",
userId, timeUtil.toReadableTime(startTime), requestUrl, methodName, requestBody);
// 실제 메서드 실행 -> Filter에서 doFilter 와 비슷함.
Object result = joinPoint.proceed();
String responseBody = toJson(result); // void면 null이 그대로 찍힘
log.info("[Admin API 응답] userId={}, url={}, responseBody={}",
userId, requestUrl, responseBody);
return result;
}
private String extractRequestBody(Object[] args) {
for (Object arg : args) {
if (arg != null && !isSimpleType(arg)) {
return toJson(arg); // DTO가 있으면 그대로
}
}
// DTO가 없으면(commentId처럼 단순 타입만 있는 경우) 파라미터 전체를 JSON으로 남김
return toJson(args);
}
private boolean isSimpleType(Object arg) {
return arg instanceof Long || arg instanceof Integer
|| arg instanceof String || arg instanceof Boolean;
}
private String toJson(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.warn("JSON 직렬화 실패: {}", e.getMessage());
return "직렬화 실패";
}
}
}
구현 내용
- @Around("execution(* org.example.expert.domain..service..*AdminService.*(..))") : UserAdminService, CommentAdminService에 적용됨. (추후 TodoAdminService 등이 추가된다면 거기도 자동 적용됨)
- userId : JwtFilter가 미리 request.setAttribute("userId", ...)로 심어둔 값을 꺼냄
- requestUrl : 현재 스레드에 바인딩된 HttpServletRequest에서 URL 추출
- requestBody : joinPoint.getArgs()(메서드 파라미터 배열)에서 DTO로 보이는 값을 찾아 JSON화
- methodName : joinPoint.getSignature()로 어떤 메서드 정보 확인
(더 자세한 코드 설명은 아래 글 참고)
https://record47584.tistory.com/95
AOP(Aspect-Oriented Programming)의 개념과 구현
AOP(Aspect-Oriented Programming)란? 여러 클래스에 공통으로 필요한 로직(로깅, 트랜잭션, 실행시간 측정 등)을 비즈니스 로직과 분리해서 한 곳에 모아두는 방식만약 모든 api의 실행 시간을 체크해야
record47584.tistory.com
결과
10:16:41.691 [INFO ] : [Admin API 요청] userId=2, time=2026-07-27 10:16:41.690, url=/admin/users/4, method=UserAdminService.changeUserRole(..), requestBody={"role":"ADMIN"}
10:16:41.711 [INFO ] : [Admin API 응답] userId=2, url=/admin/users/4, responseBody=null'Spring > - [시리즈] Spring 프로젝트 되살리기' 카테고리의 다른 글
| Spring 프로젝트 되살리기 8 - 인가(Authorization) 인터셉터 구현 (0) | 2026.07.27 |
|---|---|
| Spring 프로젝트 되살리기 7 - 코드 리팩토링 4 (record, 정적 팩토리 메서드 적용) (0) | 2026.07.26 |
| Spring 프로젝트 되살리기 6 - 유닛 테스트 코드 고치기 (0) | 2026.07.25 |
| Spring 프로젝트 되살리기 5 - 코드 리팩토링 3 (@EntityGraph 적용) (0) | 2026.07.25 |
| Spring 프로젝트 되살리기 4 - 코드 리팩토링 2 (@Valid 적용) (0) | 2026.07.25 |