From d34a6ad27e6789c55261fe4917d4ae8b9774afc5 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sun, 27 Jul 2025 22:35:07 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20API=20=EC=9A=94=EC=B2=AD=20/=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EA=B2=B0=EA=B3=BC=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/common/log/LoggingFilter.kt | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/kotlin/roomescape/common/log/LoggingFilter.kt diff --git a/src/main/kotlin/roomescape/common/log/LoggingFilter.kt b/src/main/kotlin/roomescape/common/log/LoggingFilter.kt new file mode 100644 index 00000000..ea155306 --- /dev/null +++ b/src/main/kotlin/roomescape/common/log/LoggingFilter.kt @@ -0,0 +1,65 @@ +package roomescape.common.log + +import com.fasterxml.jackson.databind.ObjectMapper +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.core.Ordered +import org.springframework.core.annotation.Order +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter +import org.springframework.web.util.ContentCachingRequestWrapper +import org.springframework.web.util.ContentCachingResponseWrapper + +private val log = KotlinLogging.logger {} + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +class LoggingFilter( + private val objectMapper: ObjectMapper +) : OncePerRequestFilter() { + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val cachedRequest = ContentCachingRequestWrapper(request) + val cachedResponse = ContentCachingResponseWrapper(response) + + val startTime = System.currentTimeMillis() + filterChain.doFilter(cachedRequest, cachedResponse) + val duration = System.currentTimeMillis() - startTime + + logAPISummary(cachedRequest, cachedResponse, duration) + cachedResponse.copyBodyToResponse() + + } + + private fun logAPISummary( + request: ContentCachingRequestWrapper, + response: ContentCachingResponseWrapper, + duration: Long + ) { + val payload = linkedMapOf( + "type" to "API_LOG", + "method" to request.method, + "url" to request.requestURL.toString(), + ) + request.queryString?.let { payload["query_params"] = it } + payload["remote_ip"] = request.remoteAddr + payload["status_code"] = response.status + payload["duration_ms"] = duration + + if (log.isDebugEnabled()) { + request.contentAsByteArray.takeIf { it.isNotEmpty() } + ?.let { payload["request_body"] = objectMapper.readValue(it, Map::class.java) } + + response.contentAsByteArray.takeIf { it.isNotEmpty() } + ?.let { payload["response_body"] = objectMapper.readValue(it, Map::class.java) } + } + + log.info { objectMapper.writeValueAsString(payload) } + } +}