feat: 컨트롤러 요청 / 응답을 기록하는 AOP

This commit is contained in:
이상진 2025-07-29 14:05:59 +09:00
parent a2fea10f50
commit 660ac5ebc9

View File

@ -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<String, Any> = 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<String, Any> {
val signature = joinPoint.signature as MethodSignature
val args = joinPoint.args
val payload = mutableMapOf<String, Any>()
payload["controller_method"] = joinPoint.signature.toShortString()
val requestParams: MutableMap<String, Any> = mutableMapOf()
val pathVariables: MutableMap<String, Any> = 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
}
}