Skip to content

Commit

Permalink
ts-loader -> esbuild-loader 마이그레이션 (#703)
Browse files Browse the repository at this point in the history
* Query Counter (aka. N+1 Detector) 를 통한 API별 쿼리 정보 정리 (#572)

* feat: N+1 detector 적용

* feat: AOP 스프링 빈 등록

* chore: rebase develop

* chore: p6spy 재적용

* refactor: info-appender logging 패턴 수정

* refactor: info-appender logging 패턴 개행 추가

* refactor: 패키지 위치 및 네이밍 변경

---------

Co-authored-by: mcodnjs <[email protected]>

* feat: ts-loader -> esbuild-loader 마이그레이션

* refactor: fork-ts-checker-webpack-plugin 동작 문제 수정

---------

Co-authored-by: 신종화 <[email protected]>
Co-authored-by: mcodnjs <[email protected]>
  • Loading branch information
3 people authored Oct 12, 2023
1 parent 766406d commit decf270
Show file tree
Hide file tree
Showing 8 changed files with 1,181 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package hanglog.global.detector;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;

@RequiredArgsConstructor
public class ConnectionProxyHandler implements MethodInterceptor {

private static final String JDBC_PREPARE_STATEMENT_METHOD_NAME = "prepareStatement";

private final Object connection;
private final LoggingForm loggingForm;

@Nullable
@Override
public Object invoke(@Nonnull final MethodInvocation invocation) throws Throwable {
final Object result = invocation.proceed();

if (hasConnection(result) && hasPreparedStatementInvoked(invocation)) {
final ProxyFactory proxyFactory = new ProxyFactory(result);
proxyFactory.addAdvice(new PreparedStatementProxyHandler(loggingForm));
return proxyFactory.getProxy();
}

return result;
}

private boolean hasPreparedStatementInvoked(final MethodInvocation invocation) {
return invocation.getMethod().getName().equals(JDBC_PREPARE_STATEMENT_METHOD_NAME);
}

private boolean hasConnection(final Object result) {
return result != null;
}

public Object getProxy() {
final ProxyFactory proxyFactory = new ProxyFactory(connection);
proxyFactory.addAdvice(this);
return proxyFactory.getProxy();
}
}
30 changes: 30 additions & 0 deletions backend/src/main/java/hanglog/global/detector/LoggingForm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package hanglog.global.detector;

import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
public class LoggingForm {

private String apiUrl;
private String apiMethod;
private Long queryCounts = 0L;
private Long queryTime = 0L;

public void queryCountUp() {
queryCounts++;
}

public void addQueryTime(final Long queryTime) {
this.queryTime += queryTime;
}

public void setApiUrl(final String apiUrl) {
this.apiUrl = apiUrl;
}

public void setApiMethod(final String apiMethod) {
this.apiMethod = apiMethod;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package hanglog.global.detector;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

@RequiredArgsConstructor
public class PreparedStatementProxyHandler implements MethodInterceptor {

private static final List<String> JDBC_QUERY_METHOD = List.of("executeQuery", "execute", "executeUpdate");

private final LoggingForm loggingForm;

@Nullable
@Override
public Object invoke(@Nonnull final MethodInvocation invocation) throws Throwable {

final Method method = invocation.getMethod();

if (JDBC_QUERY_METHOD.contains(method.getName())) {
final long startTime = System.currentTimeMillis();
final Object result = invocation.proceed();
final long endTime = System.currentTimeMillis();

loggingForm.addQueryTime(endTime - startTime);
loggingForm.queryCountUp();

return result;
}

return invocation.proceed();
}
}
60 changes: 60 additions & 0 deletions backend/src/main/java/hanglog/global/detector/QueryCounterAop.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package hanglog.global.detector;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Slf4j
@Component
public class QueryCounterAop {

private final ThreadLocal<LoggingForm> currentLoggingForm;

public QueryCounterAop() {
this.currentLoggingForm = new ThreadLocal<>();
}

@Around("execution( * javax.sql.DataSource.getConnection())")
public Object captureConnection(final ProceedingJoinPoint joinPoint) throws Throwable {
final Object connection = joinPoint.proceed();

return new ConnectionProxyHandler(connection, getCurrentLoggingForm()).getProxy();
}

private LoggingForm getCurrentLoggingForm() {
if (currentLoggingForm.get() == null) {
currentLoggingForm.set(new LoggingForm());
}

return currentLoggingForm.get();
}

@After("within(@org.springframework.web.bind.annotation.RestController *)")
public void loggingAfterApiFinish() {
final LoggingForm loggingForm = getCurrentLoggingForm();

final ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

if (isInRequestScope(attributes)) {
final HttpServletRequest request = attributes.getRequest();

loggingForm.setApiMethod(request.getMethod());
loggingForm.setApiUrl(request.getRequestURI());
}

log.info("{}", getCurrentLoggingForm());
currentLoggingForm.remove();
}

private boolean isInRequestScope(final ServletRequestAttributes attributes) {
return attributes != null;
}
}
3 changes: 1 addition & 2 deletions backend/src/main/resources/info-appender.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
</filter>
<encoder>
<pattern>
"timestamp": "%date{yyyy-MM-dd HH:mm}",
${CONSOLE_LOG_PATTERN}\n
[%d{yyyy-MM-dd HH:mm:ss.SSS}] %-5level ${PID:-} --- [%15.15thread] %-40.40logger{36} : %msg%n%n
</pattern>
<charset>utf8</charset>
</encoder>
Expand Down
7 changes: 6 additions & 1 deletion frontend/config/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const Dotenv = require('dotenv-webpack');
const webpack = require('webpack');
const { convertToAbsolutePath } = require('./webpackUtil');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
entry: convertToAbsolutePath('src/index.tsx'),
Expand All @@ -11,7 +12,10 @@ module.exports = {
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: ['ts-loader'],
loader: 'esbuild-loader',
options: {
target: 'es2021',
},
},
{
test: /\.svg$/i,
Expand Down Expand Up @@ -67,5 +71,6 @@ module.exports = {
favicon: convertToAbsolutePath('public/favicon.ico'),
}),
new Dotenv(),
new ForkTsCheckerWebpackPlugin(),
],
};
Loading

0 comments on commit decf270

Please sign in to comment.