티스토리 뷰
1. 기본 로깅의 한계
- 중복된 로깅 코드로 인한 문제점
//PostController
@PostMapping("/posts")
public ResponseEntity<SuccessStatusResponse> createPost(
@RequestHeader Long memberId,
@RequestHeader Long blogId,
@Valid @RequestBody PostCreateRequest postCreateRequest) {
log.info("Entering createPost: memberId={}, blogId={}, title={}, content={}",
memberId, blogId, postCreateRequest.title(), postCreateRequest.content());
ResponseEntity<SuccessStatusResponse> response = ResponseEntity.status(HttpStatus.CREATED)
.header("Location", postService.create(memberId, blogId, postCreateRequest))
.body(SuccessStatusResponse.of(SuccessMessage.POST_CREATE_SUCCESS));
log.info("Exiting createPost: responseStatus={}, responseBody={}",
response.getStatusCode(), response.getBody());
return response;
}
@GetMapping("/posts/{postId}")
public ResponseEntity<SuccessStatusResponse<PostFindDto>> findPost(@PathVariable Long postId) {
log.info("Entering findPost: postId={}", postId);
ResponseEntity<SuccessStatusResponse<PostFindDto>> response = ResponseEntity.status(HttpStatus.OK)
.body(SuccessStatusResponse.of(
SuccessMessage.POST_FIND_SUCCESS,
postService.findPostById(postId)));
log.info("Exiting findPost: postId={}, responseStatus={}, responseBody={}",
postId, response.getStatusCode(), response.getBody());
return response;
}
메서드의 실행 과정에서 입력된 파라미터와 반환된 결과를 로깅하면,
콘솔에 이런식으로 출력된다.
++ 눈이 침침한 분들을 위해 (확대버전)
그러나
각 컨트롤러 메서드에 로그 문을 직접 삽입하는 방식은 코드 중복을 발생시킨다.
현재 **createPost**와 findPost 메서드 모두에서 메서드의 시작과 종료 시점에 로그를 기록하는 코드가 반복적으로 사용되고 있는데, 각 메서드에 로그 관련 코드를 추가하면 코드의 가독성과 관리가 어려워진다.
- 유지보수의 어려움
로깅 로직이 비즈니스 로직과 혼재되어 있을 경우, 로깅 요구사항이 변경될 때마다 관련된 모든 메서드를 수정해야 한다.
예를 들어, 로그 포맷을 변경하거나 추가적인 정보를 로그에 포함시키고자 할 때, 모든 컨트롤러 메서드를 일일이 찾아 수정해야 하는 번거로움이 있다.
따라서 우리는 이러한 반복되는 작업을 분리하여 전처리 혹은 후처리가 가능하도록 AOP를 적용해보겠슴!!!
2. AOP란?
출처: Incheol’s TECH BLOG
AOP(Aspect-Oriented Programming)는 공통 관심사를 추상화하여 중앙에 보관하고, 필요한 곳에 동적으로 삽입하여 적용하는 기술이다.
이 접근 방식을 통해 핵심 로직과 부가 기능을 분리할 수 있으며, 이로 인해 각 기능은 단일 책임 원칙을 유지하며 독립적인 코드로 구성된다.
이러한 구조는 프로그램의 가독성과 유지보수성을 크게 향상시킨다.
특히, 로깅과 같은 반복적이고 일반적인 작업을 처리하는 데 AOP는 매우 유용하다.
로깅을 AOP를 통해 구현하면, 메소드 호출의 시작과 종료 시점에 자동으로 로그를 기록하도록 설정할 수 있다.
이로써 개발자는 로그 관리가 필요한 모든 메소드에 직접 로그 코드를 삽입하지 않아도 되므로, 코드의 중복을 줄이고, 핵심 비즈니스 로직에 더 집중할 수 있다.
또한, 로깅 설정을 변경하거나 업그레이드할 때, 중앙 집중화된 로깅 설정만 수정하면 되기 때문에 변경 관리가 수월해진다.
3. AOP 적용
1) build.gradle에 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-aop'
2) AOP 클래스 작성
//LogAop
// Lombok 어노테이션을 사용하여 로거 인스턴스를 자동으로 생성
@Slf4j
// 이 클래스를 Aspect로 선언하여 AOP 기능을 구현
@Aspect
@Component
public class LogAop {
// Pointcut 정의: org.sopt.practice.controller 패키지 내의 모든 클래스와 모든 메소드를 대상으로 함
@Pointcut("execution(* org.sopt.practice.controller.*.*(..))")
public void controllerPointcut() {}
// Before 어드바이스: 메소드 실행 전에 실행되며, 지정된 Pointcut에 해당하는 메소드에서 작동
@Before("controllerPointcut()")
public void logMethodCall(JoinPoint joinPoint) {
Method method = getMethod(joinPoint);
// 메소드 진입 시 메소드 이름을 로깅
log.info("===== Entering method: {} =====", method.getName());
// 메소드 파라미터를 로깅
logParameters(joinPoint);
}
// AfterReturning 어드바이스: 메소드가 값을 반환한 후 실행되며, 지정된 Pointcut에 해당하는 메소드에서 작동
@AfterReturning(value = "controllerPointcut()", returning = "returnObj")
public void logMethodReturn(JoinPoint joinPoint, Object returnObj) {
Method method = getMethod(joinPoint);
// 메소드 반환 시 메소드 이름을 로깅
log.info("===== Returning method: {} =====", method.getName());
// 반환된 값의 타입과 값을 로깅
logReturnValue(returnObj);
}
// JoinPoint에서 메소드 정보를 추출하는 메소드
private Method getMethod(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getMethod();
}
// 메소드 파라미터를 로깅하는 메소드
private void logParameters(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
if (args.length == 0) {
log.info("No parameters");
} else {
for (int i = 0; i < args.length; i++) {
log.info("Parameter name: {}, type: {}, value: {}",
parameterNames[i],
args[i].getClass().getSimpleName(),
args[i]);
}
}
}
// 반환된 객체를 로깅하는 메소드
private void logReturnValue(Object returnObj) {
if (returnObj != null) {
log.info("Return type: {}, value: {}", returnObj.getClass().getSimpleName(), returnObj);
} else {
log.info("Return value is null");
}
}
}
@Aspect
- @Aspect는 AOP 설정 클래스를 정의할 때 사용하는 어노테이션이다. 이 어노테이션을 사용함으로써 클래스가 Aspect, 즉 관점을 정의하는 컴포넌트임을 나타낸다.
Pointcut
- Pointcut은 Aspect의 적용 위치를 결정하는 표현식이다. 이를 통해 특정 조건(패키지, 클래스, 메소드, 어노테이션 단위 등)에 따라 Aspect가 적용될 JoinPoint를 필터링한다. 즉, Pointcut은 Aspect가 적용될 수 있는 잠재적인 지점들 중에서 실제 적용될 지점을 선택하는 필터 역할을 한다.
JoinPoint
- JoinPoint는 프로그램 실행 중 Aspect가 적용될 수 있는 실제 지점이다. 메소드 호출, 예외 발생, 필드 접근 등 다양한 실행 지점이 이에 해당한다. JoinPoint는 런타임에 메소드 정보를 포착하며, Pointcut에 의해 선별된 특정 지점에 대한 정보를 제공한다.
어드바이스 타입
@Before
- 지정된 JoinPoint(대부분 메소드 호출) 전에 실행된다. 이 어드바이스는 메소드 실행 전에 필요한 준비 작업을 수행할 수 있게 한다.
@AfterReturning
- 메소드가 예외 없이 성공적으로 결과를 반환한 후 실행된다. 이 어드바이스는 메소드의 반환 값을 처리하거나, 필요한 후처리를 수행할 때 사용된다.
3) 테스트 ㄱㄱ
//PostController
@PostMapping("/posts")
public ResponseEntity<SuccessStatusResponse> createPost(
@RequestHeader Long memberId,
@RequestHeader Long blogId,
@Valid @RequestBody PostCreateRequest postCreateRequest) {
return ResponseEntity.status(HttpStatus.CREATED).header(
"Location",
postService.create(memberId, blogId, postCreateRequest))
.body(SuccessStatusResponse.of(SuccessMessage.POST_CREATE_SUCCESS));
}
@GetMapping("/posts/{postId}")
public ResponseEntity<SuccessStatusResponse<PostFindDto>> findPost(@PathVariable Long postId) {
return ResponseEntity.status(HttpStatus.OK).body(SuccessStatusResponse.of(
SuccessMessage.POST_FIND_SUCCESS,
postService.findPostById(postId)));
}
log를 제거한 원래의 controller로 다시 돌려주고 (AOP 클래스로 이미 로깅 적용 되어있기 때문에),
먼저 @PostMapping("/posts")
메서드 호출하면..!!
로그가 잘 찍혔다. ㅎ (중간 SQL 쿼리 로그는 생략)
@GetMapping("/posts/{postId}")
메서드 호출하면..!!
뒤에가 좀 잘렸지만 로그가 잘 찍혔다. ㅎ (중간 SQL 쿼리 로그는 생략)
++ 눈이 침침한 분들을 위해(확대버전)
뒤에가 좀 잘리긴 하지만 이런식으로 로그가 잘 찍힌답니당
자 이를 통해 AOP를 사용하여 로깅을 구현하면 코드의 중복을 제거하고, 비즈니스 로직에서 로깅 코드를 분리하여 전체 코드의 깔끔함과 유지보수성을 향상시킬 수 있음을 알게 되었습니다!!!!!!
'Spring' 카테고리의 다른 글
[SPRING] Spring Data JPA로 동적 쿼리와 페이징 최적화하기: Specification과 @EntityGraph 활용 (0) | 2024.11.05 |
---|---|
[SPRING] Hibernate 지연 로딩으로 인한 JSON 직렬화 오류 해결하기 (0) | 2024.06.07 |
[SPRING] 로깅에 대해서 알아보자 (0) | 2024.05.25 |
스프링 키워드 모음 #4 (1) | 2024.05.07 |
스프링 키워드 모음 #3 (0) | 2024.04.26 |
- Total
- Today
- Yesterday
- DP
- 준영속
- 로그아웃
- SQL
- 파이썬
- 웹MVC
- 프론트엔드
- 스프링부트
- 웹 MVC
- EnumType.ORDINAL
- 회원탈퇴
- SQLD
- 자바 스프링
- 스프링 커뮤니티
- 인텔리제이
- 백준 파이썬
- 다이나믹 프로그래밍
- SQL 레벨업
- 스프링
- elasticsearch
- 백준
- 지연로딩
- 영속
- 스프링 북마크
- 로깅
- 커뮤니티
- 비영속
- JPA
- 자바
- 북마크
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |