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 + } +}