diff --git a/backend/src/main/java/com/mapbefine/mapbefine/common/config/HibernateConfig.java b/backend/src/main/java/com/mapbefine/mapbefine/common/config/HibernateConfig.java new file mode 100644 index 000000000..8bb66b0c4 --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/common/config/HibernateConfig.java @@ -0,0 +1,24 @@ +package com.mapbefine.mapbefine.common.config; + +import com.mapbefine.mapbefine.common.filter.QueryInspector; +import org.hibernate.cfg.AvailableSettings; +import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HibernateConfig { + + private final QueryInspector queryInspector; + + public HibernateConfig(QueryInspector queryInspector) { + this.queryInspector = queryInspector; + } + + @Bean + public HibernatePropertiesCustomizer hibernatePropertiesCustomizer() { + return hibernateProperties -> + hibernateProperties.put(AvailableSettings.STATEMENT_INSPECTOR, queryInspector); + } + +} diff --git a/backend/src/main/java/com/mapbefine/mapbefine/common/filter/LatencyLoggingFilter.java b/backend/src/main/java/com/mapbefine/mapbefine/common/filter/LatencyLoggingFilter.java new file mode 100644 index 000000000..48fe8fb0c --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/common/filter/LatencyLoggingFilter.java @@ -0,0 +1,45 @@ +package com.mapbefine.mapbefine.common.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +public class LatencyLoggingFilter extends OncePerRequestFilter { + + private static final Logger log = LoggerFactory.getLogger(LatencyLoggingFilter.class); + + private final LatencyRecorder latencyRecorder; + private final QueryInspector queryInspector; + + public LatencyLoggingFilter(LatencyRecorder latencyRecorder, QueryInspector queryInspector) { + this.latencyRecorder = latencyRecorder; + this.queryInspector = queryInspector; + } + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + latencyRecorder.start(); + + filterChain.doFilter(request, response); + + double latencyForSeconds = latencyRecorder.getLatencyForSeconds(); + int queryCount = queryInspector.getQueryCount(); + String requestURI = request.getRequestURI(); + + log.info("Latency : {}s, Query count : {}, Request URI : {}", latencyForSeconds, queryCount, requestURI); + MDC.clear(); + } + +} diff --git a/backend/src/main/java/com/mapbefine/mapbefine/common/filter/LatencyRecorder.java b/backend/src/main/java/com/mapbefine/mapbefine/common/filter/LatencyRecorder.java new file mode 100644 index 000000000..204085ffd --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/common/filter/LatencyRecorder.java @@ -0,0 +1,23 @@ +package com.mapbefine.mapbefine.common.filter; + +import org.springframework.stereotype.Component; + +@Component +public class LatencyRecorder { + + private final ThreadLocal threadLocal = new ThreadLocal<>(); + + public void start() { + threadLocal.set(System.currentTimeMillis()); + } + + public double getLatencyForSeconds() { + long start = threadLocal.get(); + long end = System.currentTimeMillis(); + + threadLocal.remove(); + + return (end - start) / 1000d; + } + +} diff --git a/backend/src/main/java/com/mapbefine/mapbefine/common/filter/QueryCounter.java b/backend/src/main/java/com/mapbefine/mapbefine/common/filter/QueryCounter.java new file mode 100644 index 000000000..8ebdc614e --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/common/filter/QueryCounter.java @@ -0,0 +1,20 @@ +package com.mapbefine.mapbefine.common.filter; + +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@Component +@RequestScope +public class QueryCounter { + + private int count = 0; + + public void increase() { + count++; + } + + public int getCount() { + return count; + } + +} diff --git a/backend/src/main/java/com/mapbefine/mapbefine/common/filter/QueryInspector.java b/backend/src/main/java/com/mapbefine/mapbefine/common/filter/QueryInspector.java new file mode 100644 index 000000000..fc33ab29c --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/common/filter/QueryInspector.java @@ -0,0 +1,33 @@ +package com.mapbefine.mapbefine.common.filter; + +import org.hibernate.resource.jdbc.spi.StatementInspector; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; + +@Component +public class QueryInspector implements StatementInspector { + + private final QueryCounter queryCounter; + + public QueryInspector(QueryCounter queryCounter) { + this.queryCounter = queryCounter; + } + + @Override + public String inspect(String sql) { + if (isInRequestScope()) { + queryCounter.increase(); + } + + return sql; + } + + private boolean isInRequestScope() { + return RequestContextHolder.getRequestAttributes() != null; + } + + public int getQueryCount() { + return queryCounter.getCount(); + } + +} diff --git a/backend/src/test/java/com/mapbefine/mapbefine/common/filter/LatencyRecorderTest.java b/backend/src/test/java/com/mapbefine/mapbefine/common/filter/LatencyRecorderTest.java new file mode 100644 index 000000000..3e19f9338 --- /dev/null +++ b/backend/src/test/java/com/mapbefine/mapbefine/common/filter/LatencyRecorderTest.java @@ -0,0 +1,48 @@ +package com.mapbefine.mapbefine.common.filter; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class LatencyRecorderTest { + + private final LatencyRecorder latencyRecorder = new LatencyRecorder(); + + @Test + @DisplayName("실행 시간을 초단위로 측정할 수 있다.") + void getLatencyForSeconds() throws InterruptedException { + //given + latencyRecorder.start(); + + //5초 + Thread.sleep(5000); + + //when + double latencyForSeconds = latencyRecorder.getLatencyForSeconds(); + + //then + assertThat(latencyForSeconds).isGreaterThanOrEqualTo(5) + .isLessThanOrEqualTo(6); + } + + @Test + @DisplayName("다른 쓰레드에 영향을 받지 않는다.") + void getLatencyForSecondsThreadSafe() throws InterruptedException { + //given + Thread thread = new Thread(latencyRecorder::start); + thread.start(); + Thread.sleep(3000); + + latencyRecorder.start(); + Thread.sleep(3000); + + //when + double latencyForSeconds = latencyRecorder.getLatencyForSeconds(); + + //then + assertThat(latencyForSeconds).isGreaterThanOrEqualTo(3) + .isLessThanOrEqualTo(4); + } + +} diff --git a/backend/src/test/java/com/mapbefine/mapbefine/common/filter/QueryCounterTest.java b/backend/src/test/java/com/mapbefine/mapbefine/common/filter/QueryCounterTest.java new file mode 100644 index 000000000..073af8879 --- /dev/null +++ b/backend/src/test/java/com/mapbefine/mapbefine/common/filter/QueryCounterTest.java @@ -0,0 +1,24 @@ +package com.mapbefine.mapbefine.common.filter; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class QueryCounterTest { + + @Test + @DisplayName("값을 증가시킬 수 있다.") + void increase_Success() { + //given + QueryCounter queryCounter = new QueryCounter(); + assertThat(queryCounter.getCount()).isZero(); + + //when + queryCounter.increase(); + + //then + assertThat(queryCounter.getCount()).isOne(); + } + +}