5. API 로깅
AOP -> 어떤 요청을 실행시켰는지
Interceptor -> Http 요청에 대한 기록
둘 다 해보기
(1) AOP

강의 자료에서 봤던 것 처럼 형식에 맞춰서 패키지.패키지. . . 클래스 순서로 작성
강의 자료에서는 한 클래스 내부의 메소드만 체크해줬지만 과제는 다른 두 클래스를 사용해야한다.
찾아보니까 AND 와 OR 연산자를 사용할 수 있는 듯 하여 추가
@Around("execution(* org.example.expert.domain.user.service..*(..)) ||" +
"execution(* org.example.expert.domain.comment.service..*(..))")

댓글을 삭제하는 요청을 보냈고 정상적으로 삭제되었다

로그가 찍히는 걸 확인 후 로그에 포함되어야 할 내용을 추가해줘야한다...
(첫 코드)
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class ApiLogRecordAop {
private final JwtUtil jwtUtil;
private final ObjectMapper objectMapper;
@Around("execution(* org.example.expert.domain.user.service..*(..)) ||" +
"execution(* org.example.expert.domain.comment.service..*(..))")
public Object recordApiLog(ProceedingJoinPoint joinPoint) throws Throwable{
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes()).getRequest();
log.info("timeStamp: "+ LocalDateTime.now()); # api 요청이 들어온 시각
log.info("package: {}", joinPoint.getThis()); # 패키지 위치
log.info("요청 method: {} {}", request.getMethod() ,request.getRequestURI()); # URL
String jwt = jwtUtil.substringToken(request.getHeader("Authorization"));
log.info("해당 요청에 접근한 사용자 Id: {}", jwtUtil.extractClaims(jwt).getSubject()); # ID
# Object[] 로 반복문을 돌려서
for (Object obj : joinPoint.getArgs()){
# 사용된 형태가 ./dto/request 형태인 것만 추출
if (obj.getClass().getPackageName().contains(".dto.request")){
# json 형태로 변환하여 출력
log.info("요청 본문: {}", objectMapper.writeValueAsString(obj));
break;
}
}
# -------얘 기준으로 method 앞인지 뒤인지 결정됨---------
Object result = joinPoint.proceed();
# -----------------------------------------------------
log.info("timeStamp: "+ LocalDateTime.now());
# 사용된 형태가 ./dto/response 형태인 것만 추출
for (Object obj : joinPoint.getArgs()){
if (obj.getClass().getPackageName().contains(".dto.response")){
log.info("응답 본문: {}", objectMapper.writeValueAsString(obj));
break;
}
}
return result;
}
}
과장 좀 보태서 3시간은 쓴 거 같다
리팩토링 할 부분이 너무 많아서 코드 수정 과정 lv6 TIL로 추가 예정

결과적으로 필요한 값은 출력이 되었지만 request.getXXX 를 20개는 써본 것 같다 너무 어거지로 만든 느낌 ㅠㅠ
일단 어려웠던게 HttpServletRequest 가 무슨 값을 들고있고 어떻게 참조해야할지 잘 몰랐고, 동시에 ProceedingJoinPoint 도 생소해서 어떻게 그 안 에서 원하는 정보만 빼서 log 로 출력해야할지에 대해 고민이 많았던거같음

수많은 메소드들 중 언젠가 쓸 거 같은 애들만 정리하고 넘어가려고 한다.
<HttpServletRequest>
| method Name | 의미 | 결과값 예시 |
| getRequestURI() | 요청이 들어간 URL | /admin/comments/3 |
| getMethod() | HTTP method | GET, POST, PUT 등 |
| getParameter(String name) | parameter 이름을 넣어서 해당하는 쿼리 파라미터의 value 값을 가져옴 | /login?email=tako@email.com |
| getHeader(String name) | Header 의 name을 넣어서 value 값을 가져옴 ex) "Authorization" | Bearer ey어쩌고... |
| getAttribute(String name) | request 에 저장되어있는 값 꺼내기 | |
| setAttribute(String name, Object value) | request 에 값 저장 인증 객체를 저장함 |
|
| getSession() | 세션기반 로그인 할 때 사용 |
<JoinPoint>
| method Name | 의미 | 결과값 예시 |
| getSigniture().getName() | 현재 실행중인 method 정보 | createTodo |
| getArgs() | DTO 값 로깅 | |
| getTarget().getClass().getName() | 실행중인 class 정보 (객체) | org.example.expert.domain.todo.service.TodoService |
| getSigniture().getMethod().XXX | 어노테이션을 읽음 MethodSigniture 를 받고 Method method = sig.getMethod() 로 씀 method.isAnnotationPresent(xxx.class) |
다음엔 헤매지 말아야지
(2) Interceptor
Interceptor 과제에서는 오류가 발생하면 오류처리를 하는 부분도 포함되어있기 때문에 일부러 ADMIN 권한이 없는 버전으로 실행
구현 순서
1) HandlerInterceptor 를 implements 한 CustomInterceptor 만들기
@Slf4j
@Component
@RequiredArgsConstructor
public class CheckAdminIntercepotor implements HandlerInterceptor {
# 2)
}
Bean 으로 등록해주기 위해 @Component 를 추가
2) Prehandle 또는 Posthandle 내용을 구현
private final JwtUtil jwtUtil;
public boolean preHandle (HttpServletRequest request,HttpServletResponse response,
Object handler) throws IOException{
String jwt = jwtUtil.substringToken(request.getHeader("Authorization"));
String role = (String) request.getAttribute("userRole");
log.info(role +" "+UserRole.ADMIN);
if (UserRole.of(role) != UserRole.ADMIN){
log.warn("관리자 아님, 접근 거부 {}", jwtUtil.extractClaims(jwt).getSubject());
throw new InvalidRequestException("관리자 아닌디?");
}
log.info("timestamp: {}", LocalDateTime.now());
log.info("요청 method: {} {}", request.getMethod() ,request.getRequestURI());
return true;
}
토큰을 파싱해서 현재 로그인한 유저의 역할을 role 에 저장하고 ADMIN 인지 확인하여 exception 을 던지는 로직
만약 exception 이 던져지지 않았다면 해당 요청이 온 시각과 요청 Method URI 를 출력
3) WebConfig 에 addInterceptors 메서드를 추가
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(checkAdminIntercepotor) # 어떤 인터셉터를
.addPathPatterns("/admin/**"); # 어떤 URL 에 연결할지
}
admin 이 붙은 메서드들만 interceptor 를 실시할 수 있다
** 은 그 이후에 나오는 endpoint 는 별개로 /admin 까지만 확인한다는 의미

InterceptorRegistry 는 addInterceptors의 매개변수로 사용되며 어떤 Interceptor 를 어떤 URL 패턴에 적용할지 등록
-> Interceptor -----InterceptorRegistry -----> SpringMVC (연결해주는 역할)
코드를 실행시켰다

근데 내가 설정한 log 메시지인 관리자 아님, 접근 거부 가 아닌 권한부족 log 가 발생
-> JwtFilter 에 이미 ADMIN 역할 필터링이 되는 부분이 존재해서 이미 Filter 단에서 걸려서 Interceptor 가 수행되기 전에 return 또는 throw 됨
주석처리 후 실행시키니 아래처럼 내가 설정한 값들로 제대로 return 되는 모습


결론
강의에서 최근에는 Interceptor 대신 AOP 를 많이 사용한다고 해서 이번 과제에서는 AOP 를 한 번 제대로 사용해보고싶은 마음이 컸다! 최근에 많이 사용되는게 제일 성능이나 편리함 측면에서 좋을 것이라고 생각했었던 것 같다
하지만 방법 둘 다 구현을 하는 과정에서
AOP 코드가 위치한곳과 사용하면 안 되는 로직... 들에 대해서 알고 리팩토링 하는 과정에서 많은 어려움을 겪었다
| Interceptor | Http요청 | 인증, 인가 -> 웹 요청 제어니까 Interceptor 쓰는게 55555배 나음 |
| AOP | 특정 메서드 | 공통 비즈니스 로직 (ex 로깅, 시간재기) |
AOP 는 Http 개념을 몰라야하므로 ContextHolder 까지 가서 꺼내야함
-> 너무 번거로운 작업 ....
Request 를 분석해야하는 경우에는 AOP 말고 interceptor 쓰기......
AOP 에서 requestBody 꺼내거나 컨트롤러 흐름 제어하려고 하는짓 하지말기
최신꺼 말고 무조건 상황에 맞는거 쓰기
참고자료
[Spring Boot] AOP를 활용한 로깅 구현: @EnableAspectJAutoProxy와 사용자 정의 Aspect 클래스 비교 가이드
AOP를 활용한 로깅 구현: @EnableAspectJAutoProxy와 사용자 정의 Aspect 클래스 비교 가이드 스프링 부트 프로젝트에 로직의 이해를 돕기 위해 AOP를 로깅에 적용하는 방법에 대해서 설명해 보겠다. 1. 왜 A
yijoon009.tistory.com
'SPARTA 과제 > SPRING' 카테고리의 다른 글
| 심화) LV 7 Test code (1) | 2026.03.07 |
|---|---|
| 심화) LV 6 코드 리팩토링 (0) | 2026.03.07 |
| 심화) LV 0~ LV 4 필수 문제풀이 과정 (0) | 2026.03.04 |
| 숙련) 일정관리앱 트러블슈팅 (4) (0) | 2026.02.13 |
| 숙련) 일정관리앱 트러블슈팅 (3) (0) | 2026.02.12 |