refactor: 로그 가독성 향상을 위한 컨트롤러 응답 로그에 엔드포인트 추가

This commit is contained in:
이상진 2025-09-09 09:08:16 +09:00
parent 9660d5438d
commit a4334c224f
4 changed files with 32 additions and 7 deletions

View File

@ -2,6 +2,7 @@ package roomescape.common.exception
import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import jakarta.servlet.http.HttpServletRequest
import org.slf4j.MDC import org.slf4j.MDC
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
@ -14,6 +15,7 @@ import roomescape.common.dto.response.CommonErrorResponse
import roomescape.common.log.ApiLogMessageConverter import roomescape.common.log.ApiLogMessageConverter
import roomescape.common.log.ConvertResponseMessageRequest import roomescape.common.log.ConvertResponseMessageRequest
import roomescape.common.log.LogType import roomescape.common.log.LogType
import roomescape.common.log.getEndpoint
private val log: KLogger = KotlinLogging.logger {} private val log: KLogger = KotlinLogging.logger {}
@ -22,7 +24,10 @@ class ExceptionControllerAdvice(
private val messageConverter: ApiLogMessageConverter private val messageConverter: ApiLogMessageConverter
) { ) {
@ExceptionHandler(value = [RoomescapeException::class]) @ExceptionHandler(value = [RoomescapeException::class])
fun handleRoomException(e: RoomescapeException): ResponseEntity<CommonErrorResponse> { fun handleRoomException(
servletRequest: HttpServletRequest,
e: RoomescapeException
): ResponseEntity<CommonErrorResponse> {
val errorCode: ErrorCode = e.errorCode val errorCode: ErrorCode = e.errorCode
val httpStatus: HttpStatus = errorCode.httpStatus val httpStatus: HttpStatus = errorCode.httpStatus
val errorResponse = CommonErrorResponse(errorCode) val errorResponse = CommonErrorResponse(errorCode)
@ -30,6 +35,7 @@ class ExceptionControllerAdvice(
val type = if (e is AuthException) LogType.AUTHENTICATION_FAILURE else LogType.APPLICATION_FAILURE val type = if (e is AuthException) LogType.AUTHENTICATION_FAILURE else LogType.APPLICATION_FAILURE
logException( logException(
type = type, type = type,
servletRequest = servletRequest,
httpStatus = httpStatus.value(), httpStatus = httpStatus.value(),
errorResponse = errorResponse, errorResponse = errorResponse,
exception = e exception = e
@ -41,7 +47,10 @@ class ExceptionControllerAdvice(
} }
@ExceptionHandler(value = [MethodArgumentNotValidException::class, HttpMessageNotReadableException::class]) @ExceptionHandler(value = [MethodArgumentNotValidException::class, HttpMessageNotReadableException::class])
fun handleInvalidRequestValueException(e: Exception): ResponseEntity<CommonErrorResponse> { fun handleInvalidRequestValueException(
servletRequest: HttpServletRequest,
e: Exception
): ResponseEntity<CommonErrorResponse> {
val message: String = if (e is MethodArgumentNotValidException) { val message: String = if (e is MethodArgumentNotValidException) {
e.bindingResult.allErrors e.bindingResult.allErrors
.mapNotNull { it.defaultMessage } .mapNotNull { it.defaultMessage }
@ -57,6 +66,7 @@ class ExceptionControllerAdvice(
logException( logException(
type = LogType.APPLICATION_FAILURE, type = LogType.APPLICATION_FAILURE,
servletRequest = servletRequest,
httpStatus = httpStatus.value(), httpStatus = httpStatus.value(),
errorResponse = errorResponse, errorResponse = errorResponse,
exception = e exception = e
@ -68,7 +78,10 @@ class ExceptionControllerAdvice(
} }
@ExceptionHandler(value = [Exception::class]) @ExceptionHandler(value = [Exception::class])
fun handleException(e: Exception): ResponseEntity<CommonErrorResponse> { fun handleException(
servletRequest: HttpServletRequest,
e: Exception
): ResponseEntity<CommonErrorResponse> {
log.error(e) { "[ExceptionControllerAdvice] Unexpected exception occurred: ${e.message}" } log.error(e) { "[ExceptionControllerAdvice] Unexpected exception occurred: ${e.message}" }
val errorCode: ErrorCode = CommonErrorCode.UNEXPECTED_SERVER_ERROR val errorCode: ErrorCode = CommonErrorCode.UNEXPECTED_SERVER_ERROR
@ -77,6 +90,7 @@ class ExceptionControllerAdvice(
logException( logException(
type = LogType.UNHANDLED_EXCEPTION, type = LogType.UNHANDLED_EXCEPTION,
servletRequest = servletRequest,
httpStatus = httpStatus.value(), httpStatus = httpStatus.value(),
errorResponse = errorResponse, errorResponse = errorResponse,
exception = e exception = e
@ -89,12 +103,14 @@ class ExceptionControllerAdvice(
private fun logException( private fun logException(
type: LogType, type: LogType,
servletRequest: HttpServletRequest,
httpStatus: Int, httpStatus: Int,
errorResponse: CommonErrorResponse, errorResponse: CommonErrorResponse,
exception: Exception exception: Exception
) { ) {
val commonRequest = ConvertResponseMessageRequest( val commonRequest = ConvertResponseMessageRequest(
type = type, type = type,
endpoint = servletRequest.getEndpoint(),
httpStatus = httpStatus, httpStatus = httpStatus,
startTime = MDC.get("startTime")?.toLongOrNull(), startTime = MDC.get("startTime")?.toLongOrNull(),
body = errorResponse, body = errorResponse,

View File

@ -45,6 +45,7 @@ class ApiLogMessageConverter(
fun convertToResponseMessage(request: ConvertResponseMessageRequest): String { fun convertToResponseMessage(request: ConvertResponseMessageRequest): String {
val payload: MutableMap<String, Any> = mutableMapOf() val payload: MutableMap<String, Any> = mutableMapOf()
payload["type"] = request.type payload["type"] = request.type
payload["endpoint"] = request.endpoint
payload["status_code"] = request.httpStatus payload["status_code"] = request.httpStatus
MDC.get(MDC_MEMBER_ID_KEY)?.toLongOrNull() MDC.get(MDC_MEMBER_ID_KEY)?.toLongOrNull()
@ -75,8 +76,11 @@ class ApiLogMessageConverter(
data class ConvertResponseMessageRequest( data class ConvertResponseMessageRequest(
val type: LogType, val type: LogType,
val endpoint: String,
val httpStatus: Int = 200, val httpStatus: Int = 200,
val startTime: Long? = null, val startTime: Long? = null,
val body: Any? = null, val body: Any? = null,
val exception: Exception? = null val exception: Exception? = null
) )
fun HttpServletRequest.getEndpoint(): String = "${this.method} ${this.requestURI}"

View File

@ -33,22 +33,25 @@ class ControllerLoggingAspect(
val startTime: Long = MDC.get("startTime").toLongOrNull() ?: System.currentTimeMillis() val startTime: Long = MDC.get("startTime").toLongOrNull() ?: System.currentTimeMillis()
val controllerPayload: Map<String, Any> = parsePayload(joinPoint) val controllerPayload: Map<String, Any> = parsePayload(joinPoint)
val servletRequest: HttpServletRequest = servletRequest()
log.info { log.info {
messageConverter.convertToControllerInvokedMessage(servletRequest(), controllerPayload) messageConverter.convertToControllerInvokedMessage(servletRequest, controllerPayload)
} }
try { try {
return joinPoint.proceed() return joinPoint.proceed()
.also { logSuccess(startTime, it) } .also { logSuccess(servletRequest.getEndpoint(), startTime, it) }
} catch (e: Exception) { } catch (e: Exception) {
throw e throw e
} }
} }
private fun logSuccess(startTime: Long, result: Any) { private fun logSuccess(endpoint: String, startTime: Long, result: Any) {
val responseEntity = result as ResponseEntity<*> val responseEntity = result as ResponseEntity<*>
var convertResponseMessageRequest = ConvertResponseMessageRequest( var convertResponseMessageRequest = ConvertResponseMessageRequest(
type = LogType.CONTROLLER_SUCCESS, type = LogType.CONTROLLER_SUCCESS,
endpoint = endpoint,
httpStatus = responseEntity.statusCode.value(), httpStatus = responseEntity.statusCode.value(),
startTime = startTime, startTime = startTime,
) )

View File

@ -50,14 +50,16 @@ class ApiLogMessageConverterTest : StringSpec({
} }
"Controller 응답 메시지를 반환한다." { "Controller 응답 메시지를 반환한다." {
val endpoint = "POST /test/sangdol"
val request = ConvertResponseMessageRequest( val request = ConvertResponseMessageRequest(
type = LogType.CONTROLLER_SUCCESS, type = LogType.CONTROLLER_SUCCESS,
endpoint = endpoint,
httpStatus = 200, httpStatus = 200,
exception = AuthException(AuthErrorCode.MEMBER_NOT_FOUND, "테스트 메시지!") exception = AuthException(AuthErrorCode.MEMBER_NOT_FOUND, "테스트 메시지!")
) )
converter.convertToResponseMessage(request) shouldBe """ converter.convertToResponseMessage(request) shouldBe """
{"type":"CONTROLLER_SUCCESS","status_code":200,"member_id":1,"exception":{"class":"AuthException","message":"테스트 메시지!"}} {"type":"CONTROLLER_SUCCESS","endpoint":"$endpoint","status_code":200,"member_id":1,"exception":{"class":"AuthException","message":"테스트 메시지!"}}
""".trimIndent() """.trimIndent()
} }
}) })