From 660ac5ebc9623a1917062bbbed83bd17dc6cb618 Mon Sep 17 00:00:00 2001 From: pricelees Date: Tue, 29 Jul 2025 14:05:59 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20/=20=EC=9D=91=EB=8B=B5=EC=9D=84=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=ED=95=98=EB=8A=94=20AOP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/log/ControllerLoggingAspect.kt | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/main/kotlin/roomescape/common/log/ControllerLoggingAspect.kt diff --git a/src/main/kotlin/roomescape/common/log/ControllerLoggingAspect.kt b/src/main/kotlin/roomescape/common/log/ControllerLoggingAspect.kt new file mode 100644 index 00000000..c8cf4e45 --- /dev/null +++ b/src/main/kotlin/roomescape/common/log/ControllerLoggingAspect.kt @@ -0,0 +1,94 @@ +package roomescape.common.log + +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.servlet.http.HttpServletRequest +import org.aspectj.lang.JoinPoint +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.aspectj.lang.reflect.MethodSignature +import org.slf4j.MDC +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.context.request.RequestContextHolder +import org.springframework.web.context.request.ServletRequestAttributes + +private val log: KLogger = KotlinLogging.logger {} + +@Aspect +class ControllerLoggingAspect( + private val messageConverter: ApiLogMessageConverter, +) { + + @Pointcut("execution(* roomescape..web..*Controller.*(..))") + fun allController() { + } + + @Around("allController()") + fun logAPICalls(joinPoint: ProceedingJoinPoint): Any? { + val startTime: Long = MDC.get("startTime").toLongOrNull() ?: System.currentTimeMillis() + val controllerPayload: Map = parsePayload(joinPoint) + + log.info { + messageConverter.convertToControllerInvokedMessage(servletRequest(), controllerPayload) + } + + try { + return joinPoint.proceed() + .also { logSuccess(startTime, it) } + } catch (e: Exception) { + throw e + } + } + + private fun logSuccess(startTime: Long, result: Any) { + val responseEntity = result as ResponseEntity<*> + val logMessage = messageConverter.convertToResponseMessage( + ConvertResponseMessageRequest( + type = LogType.CONTROLLER_SUCCESS, + httpStatus = responseEntity.statusCode.value(), + startTime = startTime, + body = responseEntity.body + ) + ) + + log.info { logMessage } + } + + private fun servletRequest(): HttpServletRequest { + return (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request + } + + private fun parsePayload(joinPoint: JoinPoint): Map { + val signature = joinPoint.signature as MethodSignature + val args = joinPoint.args + val payload = mutableMapOf() + payload["controller_method"] = joinPoint.signature.toShortString() + + val requestParams: MutableMap = mutableMapOf() + val pathVariables: MutableMap = mutableMapOf() + signature.method.parameters.forEachIndexed { index, parameter -> + val arg = args[index] + + parameter.getAnnotation(RequestBody::class.java)?.let { + payload["request_body"] = arg + } + + parameter.getAnnotation(PathVariable::class.java)?.let { + pathVariables[parameter.name] = arg + } + + parameter.getAnnotation(RequestParam::class.java)?.let { + requestParams[parameter.name] = arg + } + } + if (pathVariables.isNotEmpty()) payload["path_variable"] = pathVariables + if (requestParams.isNotEmpty()) payload["request_param"] = requestParams + + return payload + } +}