Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

요청, 응답 로그 설정 #481

Merged
merged 25 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f54d2b3
feat: (#402) 개발 환경에서 에러 로그 분리
woo-chang Aug 15, 2023
3c17a29
feat: (#402) 운영 환경에서 에러 로그 분리
woo-chang Aug 15, 2023
f0fe020
feat: (#402) 운영 환경에서 에러 레벨 로그 발생 시 슬랙 알림 기능 구현
woo-chang Aug 15, 2023
97acd64
Merge branch 'dev' into feat/#402
woo-chang Aug 16, 2023
a6f3980
refactor: (#402) log4j2 슬랙 알림 컴포넌트 디렉토리 변경
woo-chang Aug 16, 2023
14e78cd
feat: (#402) 요청, 응답 로그 기능 구현
woo-chang Aug 16, 2023
6f4c26d
chore: (#402) 코드 컨벤션 정리
woo-chang Aug 16, 2023
f70e421
feat: (#402) memberId 컨텐스트 저장
woo-chang Aug 17, 2023
73b499a
fix: (#402) 멀티파트 요청 시 입력 스트림 읽지 않도록하여 part 요청을 읽을 수 없었던 문제 해결
woo-chang Aug 20, 2023
9f8323e
refactor: (#402) 회원 로그 식별 및 로그 가독성 향상
woo-chang Aug 21, 2023
86fd8ef
Merge branch 'dev' into feat/#402
woo-chang Aug 21, 2023
01855f6
Merge branch 'dev' into feat/#402
woo-chang Sep 19, 2023
4b1c7cc
fix: (#402) 테스트 로그 mocking 해결
woo-chang Sep 20, 2023
1e221ec
refactor: (#402) 불필요한 로그 출력 제거
woo-chang Sep 20, 2023
ab5fe36
refactor: (#402) Cors 필터 추가
woo-chang Sep 20, 2023
33df9b1
Merge branch 'dev' into feat/#402
woo-chang Sep 22, 2023
c46cb72
refactor: (#402) 사용하지 않는 설정 제거
woo-chang Oct 4, 2023
2c9ff8e
Merge branch 'dev' into feat/#402
woo-chang Oct 4, 2023
809a6dc
refactor: (#402) 사용하지 않는 로거 제거
woo-chang Oct 4, 2023
e3cd7a7
refactor: (#402) 개행을 통한 로그 가독성 향상
woo-chang Oct 5, 2023
75cb541
refactor: (#402) 메서드별 실행 시간 확인 로그 추가
woo-chang Oct 5, 2023
b084845
refactor: (#402) 유틸 클래스 생성자 접근 제어자 수정
woo-chang Oct 5, 2023
7e452ba
refactor: (#402) 불필요한 LogContextHolder 제거
woo-chang Oct 5, 2023
548ec20
refactor: (#402) 불필요한 테스트 어노테이션 제거
woo-chang Oct 5, 2023
ad952d9
refactor: (#402) 사용하지 않는 CorsRegistry 제거
woo-chang Oct 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
어떤 이유로 webflux의존성을 추가하셨나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

운영 환경에서 error 레벨의 로그가 남겨지면 즉각 대응이 필요한 상황이기에 이를 바로 알리기 위해 슬랙 알림이 필요하였습니다!

저희는 Logback이 아닌 Log4j2를 사용하고 있기 때문에 LogbackSlackAppender 라이브러리를 사용할 수 없었습니다. Log4j2SlackAppender 라이브러리가 존재하는지 찾아보았으나, 오래된 라이브러리만 존재했고 적절한 라이브러리를 찾을 수 없어서 직접 구현해서 사용하기로 결정하였습니다. 마침 직접 구현해서 사용한 분이 정리한 글이 있어 해당 글을 참고하였습니다.

슬랙 알림을 보내기 위해 HTTP 통신이 필요한 상황에서 3가지의 선택지가 존재했습니다.

  • RestTemplate
  • WebClient
  • FeignClient

RestTemplate, FeignClient는 기본적으로 Blocking 방식으로 동작하기 때문에 슬랙 알림을 위해 제어권을 뺏기게 되는 상황으로 성능에 영향을 줄 수 있습니다. 알림은 비동기로 처리해도 되는 로직이기에 비동기를 지원하는 WebClient의 선택이 필요했고, 이를 사용하기 위해 webflux 의존성을 추가하게 되었습니다 :)

Copy link
Collaborator Author

@woo-chang woo-chang Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존에 존재하는 web 의존성과 충돌이 나지 않을까라는 걱정도 했던 것 같아요.

2개의 의존성이 모두 존재하는 경우, 스프링 부트 애플리케이션은 SERVLET 방식을 선택하기 때문에 Netty 서버가 아닌 Tomcat 서버로 동작하게 되는 것을 문서를 통해 확인할 수 있었고, Tomcat 서버로 가동하면서 WebClient 기술만 사용할 수 있다는 것도 알게 되었습니다!

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'

modules {
module("org.springframework.boot:spring-boot-starter-logging") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
package com.votogether.global.config;

import com.votogether.global.jwt.JwtAuthenticationFilter;
import com.votogether.global.jwt.JwtAuthorizationArgumentResolver;
import com.votogether.global.jwt.TokenProcessor;
import com.votogether.global.log.context.MemberIdHolder;
import com.votogether.global.log.presentation.RequestLogInterceptor;
import com.votogether.global.log.presentation.RequestResponseCacheFilter;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@RequiredArgsConstructor
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

Expand All @@ -16,25 +27,57 @@ public class WebMvcConfig implements WebMvcConfigurer {
private static final String DEV_SERVER = "https://dev.votogether.com";
private static final String PROD_SERVER = "https://votogether.com";

private final MemberIdHolder memberIdHolder;
private final TokenProcessor tokenProcessor;
private final RequestLogInterceptor requestLogInterceptor;
private final JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver;

public WebMvcConfig(final JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver) {
this.jwtAuthorizationArgumentResolver = jwtAuthorizationArgumentResolver;
@Bean
public FilterRegistrationBean<CorsFilter> corsFilterRegistrationBean() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
해당 메서드에서 LOCALHOST_FRONTEND url에 대한 cors 설정을 하는 것으로 보이네요. 그런데 이미 addCorsMappings() 메서드에서 CorsRegistry에 LOCALHOST_FRONTEND에 대한 CORS 설정이 되어있는데 여기서도 설정을 한 번 더 하는 이유가 궁금합니다!

혹시 setOrder(0)을 통해 가장 먼저 해당 필터를 거치게 하기 위한 것인지, 그게 맞다면 왜 그렇게 하는 것인지, 아니면 따로 더 설정해줘야 하는 부분들이 있어서 그런지, 아니면 아예 다른 이유가 따로 있는 것인지 궁금해요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개발 환경, 운영 환경에서는 CORS를 허용해주는 역할을 하고 있는 Nginx를 리버스 프록시 서버로 두고 있기 때문에 애플리케이션 자체적으로 CORS 설정이 필요하지 않습니다!

하지만 로컬에서 8080 포트로 스프링 부트 애플리케이션을 실행시키는 경우, 리버스 프록시 서버가 없기 때문에 localhost:3000에서 실행되는 프론트 요청에 대해 CORS 에러가 발생하게 되었습니다. 분명히 CorsRegistry를 설정했음에도 왜 CORS 에러가 발생하는지 궁금하여 이에 대해 알아보게 되었습니다.

현재 저희는 인증을 위해 JwtFilter를 사용하고 있습니다! 클라이언트의 요청이 들어오면 필터 -> 서블릿 -> 인터셉터 -> 핸들러 순으로 요청이 전달되는데, CorsRegistry는 DispatcherServlet이 실행시키기 때문에 필터에서 요청이 전달되지 않으면 CORS 에러가 발생하게 됩니다.

현재 JwtFilter에는 Preflight 요청에 대한 처리가 되어있지 않아서 토큰이 존재하지 않다는 에러를 발생시키게 되고 CorsRegistry까지 요청이 전달되지 못합니다. 따라서 문제를 해결하기 위해 가장 앞단에 CorsFilter를 두어 이에 대한 처리가 가능하도록 설정하였습니다 :)

final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin(LOCALHOST_FRONTEND);
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(6000L);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
100분으로 설정하신 이유가 있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로컬에서만 사용되는 기능이기 때문에 큰 의도없이 설정한 값입니다!

해당 값을 어느 정도로 하면 좋을까요? 🤔


final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);

final FilterRegistrationBean<CorsFilter> filterBean = new FilterRegistrationBean<>(new CorsFilter(source));
filterBean.setOrder(0);

return filterBean;
}

@Bean
public FilterRegistrationBean<RequestResponseCacheFilter> requestResponseCacheFilter() {
final FilterRegistrationBean<RequestResponseCacheFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new RequestResponseCacheFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}

@Bean
public FilterRegistrationBean<JwtAuthenticationFilter> jwtAuthenticationFilter() {
final FilterRegistrationBean<JwtAuthenticationFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new JwtAuthenticationFilter(memberIdHolder, tokenProcessor));
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(2);
return filterRegistrationBean;
}

@Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(jwtAuthorizationArgumentResolver);
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(requestLogInterceptor)
.addPathPatterns("/**")
.order(1);
}

@Override
public void addCorsMappings(final CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedOrigins(HTTPS_LOCALHOST_FRONTEND, LOCALHOST_FRONTEND, DEV_SERVER, PROD_SERVER)
.allowedMethods("*")
.allowCredentials(true)
.exposedHeaders(HttpHeaders.LOCATION, HttpHeaders.SET_COOKIE);
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(jwtAuthorizationArgumentResolver);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.votogether.global.jwt;

import com.votogether.global.log.context.MemberIdHolder;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -12,11 +13,9 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@RequiredArgsConstructor
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private static final List<String> ALLOWED_URIS = List.of(
Expand All @@ -43,6 +42,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
)
);

private final MemberIdHolder memberIdHolder;
private final TokenProcessor tokenProcessor;

@Override
Expand All @@ -54,6 +54,8 @@ protected void doFilterInternal(
final String token = request.getHeader(HttpHeaders.AUTHORIZATION);
final String tokenWithoutType = tokenProcessor.resolveToken(token);
tokenProcessor.validateToken(tokenWithoutType);
final TokenPayload tokenPayload = tokenProcessor.parseToken(tokenWithoutType);
memberIdHolder.setId(tokenPayload.memberId());
Comment on lines +57 to +58
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
어차피 여러 사용자가 요청을 보낼 때마다 MemberIdHolder의 id가 계속해서 바뀔 것 같은데, 사용자가 요청을 보낼 때마다 memberIdHolder에 해당 사용자의 id를 set 하는 이유가 궁금합니다.

Copy link
Collaborator Author

@woo-chang woo-chang Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RequestScope와 관련있습니다 !

filterChain.doFilter(request, response);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.votogether.global.log.aop;

import com.votogether.global.log.context.QueryCount;
import java.sql.PreparedStatement;
import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;

@RequiredArgsConstructor
public class ConnectionMethodInterceptor implements MethodInterceptor {

private final QueryCount queryCount;

@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
final Object result = invocation.proceed();
if (result instanceof PreparedStatement ps) {
final ProxyFactory proxyFactory = new ProxyFactory(ps);
proxyFactory.addAdvice(new PreparedStatementMethodInterceptor(queryCount));
return proxyFactory.getProxy();
}
return result;
}

}
70 changes: 70 additions & 0 deletions backend/src/main/java/com/votogether/global/log/aop/LogAop.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.votogether.global.log.aop;

import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;

@Slf4j
@RequiredArgsConstructor
@Aspect
@Component
public class LogAop {

private static final String PROXY = "Proxy";

private final Logger logger;

@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 어노테이션은 어떤 역할을 하나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pointcut을 설정하여 AOP를 통해 수행될 작업을 어느 곳에 적용할지를 결정할 수 있습니다.

Pointcut 표현식이 존재하는데, 위의 표현식은 RestController 어노테이션을 가진 곳에 적용한다는 의미를 가지고 있습니다 !

public void restControllerAnnotatedClass() {
}

@Pointcut("@within(org.springframework.stereotype.Service)")
public void serviceAnnotatedClass() {
}

@Pointcut("execution(* com.votogether.domain..*Repository+.*(..))")
public void repositoryClass() {
}

@Around("restControllerAnnotatedClass() || serviceAnnotatedClass() || repositoryClass()")
public Object doLog(final ProceedingJoinPoint joinPoint) throws Throwable {
if (!isRequestScope()) {
return joinPoint.proceed();
}
final String className = getClassSimpleName(joinPoint);
final String methodName = getMethodName(joinPoint);
logger.methodCall(className, methodName);
try {
final Object result = joinPoint.proceed();
logger.methodReturn(className, methodName);
return result;
} catch (Throwable e) {
logger.throwException(className, methodName, e);
throw e;
}
}

private boolean isRequestScope() {
return Objects.nonNull(RequestContextHolder.getRequestAttributes());
}

private String getClassSimpleName(final ProceedingJoinPoint joinPoint) {
final Class<?> clazz = joinPoint.getTarget().getClass();
String className = clazz.getSimpleName();
if (className.contains(PROXY)) {
className = clazz.getInterfaces()[0].getSimpleName();
}
return className;
}

private String getMethodName(final ProceedingJoinPoint joinPoint) {
return joinPoint.getSignature().getName();
}

}
56 changes: 56 additions & 0 deletions backend/src/main/java/com/votogether/global/log/aop/Logger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.votogether.global.log.aop;

import com.votogether.global.log.context.LogContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@RequiredArgsConstructor
@Component
public class Logger {

private static final String DOT = ".";
private static final String PARENTHESES = "()";
private static final String CALL_PREFIX = "--->";
private static final String RETURN_PREFIX = "<---";
private static final String EX_PREFIX = "<X--";

private final LogContext logContext;

public void methodCall(final String className, final String methodName) {
logContext.increaseMethodCall();
log.info("[{}] {}",
logContext.getLogId(),
formattedClassAndMethod(logContext.depthPrefix(CALL_PREFIX), className, methodName)
);
}

public void methodReturn(final String className, final String methodName) {
final long currentTimeMillis = System.currentTimeMillis();
log.info("[{}] {} - [execution time = {}ms] [total time = {}ms]",
logContext.getLogId(),
formattedClassAndMethod(logContext.depthPrefix(RETURN_PREFIX), className, methodName),
logContext.executionTime(currentTimeMillis),
logContext.totalTakenTime(currentTimeMillis)
);
logContext.decreaseMethodCall();
}

public void throwException(final String className, final String methodName, final Throwable exception) {
final long currentTimeMillis = System.currentTimeMillis();
log.warn("[{}] {} - [execution time = {}ms] [total time = {}ms] [throws {}]",
logContext.getLogId(),
formattedClassAndMethod(logContext.depthPrefix(EX_PREFIX), className, methodName),
logContext.executionTime(currentTimeMillis),
logContext.totalTakenTime(currentTimeMillis),
exception.getClass().getSimpleName()
);
logContext.decreaseMethodCall();
}

private String formattedClassAndMethod(final String prefix, final String className, final String methodName) {
return prefix + className + DOT + methodName + PARENTHESES;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.votogether.global.log.aop;

import com.votogether.global.log.context.QueryCount;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.web.context.request.RequestContextHolder;

@RequiredArgsConstructor
public class PreparedStatementMethodInterceptor implements MethodInterceptor {

private static final Set<String> QUERY_METHODS = new HashSet<>(List.of("execute", "executeQuery", "executeUpdate"));

private final QueryCount queryCount;

@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2
아까 같이 얘기했던 것처럼 해당 메서드의 로직을 ConnectionMethodInterceptor의 invoke 메서드에 합쳐도 괜찮을 것 같다는 생각이 드네요. 또 한편으로는 역할을 나눠서 응집도를 높인다는 관점에선 현재 로직도 나쁘지 않다는 생각도 드네요.

다즐의 의견이 궁금합니다 :)

Copy link
Collaborator Author

@woo-chang woo-chang Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

같이 얘기를 나눴던 대로 하나의 로직으로 합쳐도 괜찮을 것 같다는 생각이 들어요 !

2가지 방법 모두 괜찮은 방법이라고 생각이 들어서 결정하기 힘들기 때문에 우선은 그대로 가는 방향이 좋을 것 같아요 ㅎㅎ

if (isExecuteQuery(invocation.getMethod()) && isRequestScope()) {
queryCount.increase();
}
return invocation.proceed();
}

private boolean isExecuteQuery(final Method method) {
final String methodName = method.getName();
return QUERY_METHODS.contains(methodName);
}

private boolean isRequestScope() {
return Objects.nonNull(RequestContextHolder.getRequestAttributes());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.votogether.global.log.aop;

import com.votogether.global.log.context.QueryCount;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.stereotype.Component;

@Aspect
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
이 어노테이션은 어떤 역할을 하나요?

Copy link
Collaborator Author

@woo-chang woo-chang Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Configuration 어노테이션과 같이 AOP에 대한 행위를 담고 있는 클래스임을 알려주는 어노테이션입니다 !

해당 클래스 내부에는 어떤 행위를 수행할지에 대한 Advice와 어떤 곳에 행위를 적용할지에 대한 Pointcut 정보가 담겨있어요 :)

@Component
public class QueryCountAop {

private final QueryCount queryCount;

public QueryCountAop(final QueryCount queryCount) {
this.queryCount = queryCount;
}

@Around("execution(* javax.sql.DataSource.getConnection())")
public Object getConnection(final ProceedingJoinPoint joinPoint) throws Throwable {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
ProceedingJoinPoint는 어떤 객체인가요?

Copy link
Collaborator Author

@woo-chang woo-chang Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JoinPoint 인터페이스는 호출하는 메서드에 대한 정보를 가지고 있는 인터페이스입니다.

@Around를 사용하는 경우 특정 메서드 전, 후의 동작을 설정할 수 있는데, @Around가 적용된 메서드의 경우 파라미터로 ProceedingJoinPoint를 사용한다고 합니다. 이를 통해 원래의 메서드를 호출하거나, 파라미터 목록 등을 확인할 수 있습니다 !

final Object connection = joinPoint.proceed();
final ProxyFactory proxyFactory = new ProxyFactory(connection);
proxyFactory.addAdvice(new ConnectionMethodInterceptor(queryCount));
return proxyFactory.getProxy();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.votogether.global.log.context;

import java.util.UUID;

public class AnonymousLogId implements LogId {

private static final String POSTFIX = "(anonymous)";

private final String id;

public AnonymousLogId() {
this.id = UUID.randomUUID().toString().substring(0, 8);
}

Comment on lines +11 to +14
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
UUID를 8자리에서 끊은 이유가 있나요?

Copy link
Collaborator Author

@woo-chang woo-chang Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

큰 의도는 존재하지 않았습니다 😀

홀수보다는 안정성 있는 짝수로 설정하고 싶었고, 8자리 정도면 같은 사용자가 여러 요청을 동시에 보내더라도 랜덤하게 설정된 값으로 유니크하지 않을까라는 생각으로 8자리를 선택했던 것 같습니다 ㅎㅎ

@Override
public String getId() {
return id + POSTFIX;
}

}
Loading