generated from pricelees/issue-pr-template
feat: LogPayloadBuilder를 기반으로 기존의 ApiLogMessageConverter 클래스 개선
This commit is contained in:
parent
6cd269e772
commit
eeb87e1bc3
@ -1,77 +0,0 @@
|
||||
package com.sangdol.common.log.message
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.sangdol.common.log.config.LogType
|
||||
import com.sangdol.common.utils.MdcPrincipalIdUtil
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
|
||||
class ApiLogMessageConverter(
|
||||
private val objectMapper: ObjectMapper
|
||||
) {
|
||||
fun convertToHttpRequestMessage(
|
||||
request: HttpServletRequest
|
||||
): String {
|
||||
val payload: MutableMap<String, Any> = commonRequestPayload(LogType.INCOMING_HTTP_REQUEST, request)
|
||||
|
||||
request.queryString?.let { payload["query_params"] = it }
|
||||
payload["client_ip"] = request.remoteAddr
|
||||
payload["user_agent"] = request.getHeader("User-Agent")
|
||||
|
||||
return objectMapper.writeValueAsString(payload)
|
||||
}
|
||||
|
||||
fun convertToControllerInvokedMessage(
|
||||
request: HttpServletRequest,
|
||||
controllerPayload: Map<String, Any>,
|
||||
): String {
|
||||
val payload: MutableMap<String, Any> = commonRequestPayload(LogType.CONTROLLER_INVOKED, request)
|
||||
val memberId: Long? = MdcPrincipalIdUtil.extractAsLongOrNull()
|
||||
if (memberId != null) payload["principal_id"] = memberId else payload["principal_id"] = "NONE"
|
||||
|
||||
payload.putAll(controllerPayload)
|
||||
|
||||
return objectMapper.writeValueAsString(payload)
|
||||
}
|
||||
|
||||
fun convertToResponseMessage(request: ConvertResponseMessageRequest): String {
|
||||
val payload: MutableMap<String, Any> = mutableMapOf()
|
||||
payload["type"] = request.type
|
||||
payload["endpoint"] = request.endpoint
|
||||
payload["status_code"] = request.httpStatus
|
||||
|
||||
MdcPrincipalIdUtil.extractAsLongOrNull()
|
||||
?.let { payload["principal_id"] = it }
|
||||
?: run { payload["principal_id"] = "NONE" }
|
||||
|
||||
request.startTime?.let { payload["duration_ms"] = System.currentTimeMillis() - it }
|
||||
request.body?.let { payload["response_body"] = it }
|
||||
request.exception?.let {
|
||||
payload["exception"] = mapOf(
|
||||
"class" to it.javaClass.simpleName,
|
||||
"message" to it.message
|
||||
)
|
||||
}
|
||||
|
||||
return objectMapper.writeValueAsString(payload)
|
||||
}
|
||||
|
||||
private fun commonRequestPayload(
|
||||
logType: LogType,
|
||||
request: HttpServletRequest
|
||||
): MutableMap<String, Any> = mutableMapOf(
|
||||
"type" to logType,
|
||||
"method" to request.method,
|
||||
"uri" to request.requestURI
|
||||
)
|
||||
}
|
||||
|
||||
data class ConvertResponseMessageRequest(
|
||||
val type: LogType,
|
||||
val endpoint: String,
|
||||
val httpStatus: Int = 200,
|
||||
val startTime: Long? = null,
|
||||
val body: Any? = null,
|
||||
val exception: Exception? = null
|
||||
)
|
||||
|
||||
fun HttpServletRequest.getEndpoint(): String = "${this.method} ${this.requestURI}"
|
||||
@ -1,69 +0,0 @@
|
||||
package com.sangdol.common.log
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.sangdol.common.log.config.LogType
|
||||
import com.sangdol.common.log.message.ApiLogMessageConverter
|
||||
import com.sangdol.common.log.message.ConvertResponseMessageRequest
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.slf4j.MDC
|
||||
|
||||
class ApiLogMessageConverterTest : StringSpec({
|
||||
val converter = ApiLogMessageConverter(jacksonObjectMapper())
|
||||
val request: HttpServletRequest = mockk()
|
||||
|
||||
beforeTest {
|
||||
MDC.remove("principal_id")
|
||||
MDC.put("principal_id", "1")
|
||||
}
|
||||
|
||||
afterSpec {
|
||||
MDC.remove("principal_id")
|
||||
}
|
||||
|
||||
"HTTP 요청 메시지를 변환한다." {
|
||||
val method = "POST".also { every { request.method } returns it }
|
||||
val requestURI = "/test/sangdol".also { every { request.requestURI } returns it }
|
||||
val clientIP = "127.0.0.1".also { every { request.remoteAddr } returns it }
|
||||
val query = "key=value&key1=value1".also { every { request.queryString } returns it }
|
||||
val userAgent = "Mozilla/5.".also { every { request.getHeader("User-Agent") } returns it }
|
||||
|
||||
converter.convertToHttpRequestMessage(request) shouldBe """
|
||||
{"type":"INCOMING_HTTP_REQUEST","method":"$method","uri":"$requestURI","query_params":"$query","client_ip":"$clientIP","user_agent":"$userAgent"}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
"Controller 요청 메시지를 변환한다." {
|
||||
val controllerPayload: Map<String, Any> = mapOf(
|
||||
"controller_method" to "Controller 요청 메시지를 변환한다.",
|
||||
"request_body" to mapOf("key1" to "value1")
|
||||
)
|
||||
val method = "POST".also { every { request.method } returns it }
|
||||
val requestURI = "/test/sangdol".also { every { request.requestURI } returns it }
|
||||
|
||||
converter.convertToControllerInvokedMessage(request, controllerPayload) shouldBe """
|
||||
{"type":"CONTROLLER_INVOKED","method":"$method","uri":"$requestURI","principal_id":1,"controller_method":"${
|
||||
controllerPayload.get(
|
||||
"controller_method"
|
||||
)
|
||||
}","request_body":{"key1":"value1"}}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
"Controller 응답 메시지를 반환한다." {
|
||||
val endpoint = "POST /test/sangdol"
|
||||
val request = ConvertResponseMessageRequest(
|
||||
type = LogType.CONTROLLER_SUCCESS,
|
||||
endpoint = endpoint,
|
||||
httpStatus = 200,
|
||||
exception = RuntimeException("테스트 메시지!")
|
||||
)
|
||||
|
||||
converter.convertToResponseMessage(request) shouldBe """
|
||||
{"type":"CONTROLLER_SUCCESS","endpoint":"$endpoint","status_code":200,"principal_id":1,"exception":{"class":"RuntimeException","message":"테스트 메시지!"}}
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
@ -1,9 +1,9 @@
|
||||
package com.sangdol.common.log.config
|
||||
package com.sangdol.common.web.config
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.sangdol.common.log.message.ApiLogMessageConverter
|
||||
import com.sangdol.common.log.web.ControllerLoggingAspect
|
||||
import com.sangdol.common.log.web.HttpRequestLoggingFilter
|
||||
import com.sangdol.common.web.asepct.ControllerLoggingAspect
|
||||
import com.sangdol.common.web.servlet.HttpRequestLoggingFilter
|
||||
import com.sangdol.common.web.support.log.WebLogMessageConverter
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
@ -12,27 +12,27 @@ import org.springframework.core.Ordered
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
|
||||
@Configuration
|
||||
class LogConfiguration {
|
||||
class WebLoggingConfig {
|
||||
|
||||
@Bean
|
||||
@DependsOn(value = ["apiLogMessageConverter"])
|
||||
@DependsOn(value = ["webLogMessageConverter"])
|
||||
fun filterRegistrationBean(
|
||||
apiLogMessageConverter: ApiLogMessageConverter
|
||||
webLogMessageConverter: WebLogMessageConverter
|
||||
): FilterRegistrationBean<OncePerRequestFilter> {
|
||||
val filter = HttpRequestLoggingFilter(apiLogMessageConverter)
|
||||
val filter = HttpRequestLoggingFilter(webLogMessageConverter)
|
||||
|
||||
return FilterRegistrationBean<OncePerRequestFilter>(filter)
|
||||
.apply { this.order = Ordered.HIGHEST_PRECEDENCE + 2 }
|
||||
}
|
||||
|
||||
@Bean
|
||||
@DependsOn(value = ["apiLogMessageConverter"])
|
||||
fun apiLoggingAspect(apiLogMessageConverter: ApiLogMessageConverter): ControllerLoggingAspect {
|
||||
return ControllerLoggingAspect(apiLogMessageConverter)
|
||||
@DependsOn(value = ["webLogMessageConverter"])
|
||||
fun apiLoggingAspect(webLogMessageConverter: WebLogMessageConverter): ControllerLoggingAspect {
|
||||
return ControllerLoggingAspect(webLogMessageConverter)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun apiLogMessageConverter(objectMapper: ObjectMapper): ApiLogMessageConverter {
|
||||
return ApiLogMessageConverter(objectMapper)
|
||||
fun webLogMessageConverter(objectMapper: ObjectMapper): WebLogMessageConverter {
|
||||
return WebLogMessageConverter(objectMapper)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package com.sangdol.common.web.support.log
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.sangdol.common.log.constant.LogType
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
|
||||
class WebLogMessageConverter(
|
||||
private val objectMapper: ObjectMapper
|
||||
) {
|
||||
fun convertToHttpRequestMessage(servletRequest: HttpServletRequest): String {
|
||||
val payload = LogPayloadBuilder(type = LogType.INCOMING_HTTP_REQUEST, servletRequest = servletRequest)
|
||||
.endpoint()
|
||||
.queryString()
|
||||
.clientIp()
|
||||
.userAgent()
|
||||
.build()
|
||||
|
||||
return objectMapper.writeValueAsString(payload)
|
||||
}
|
||||
|
||||
fun convertToControllerInvokedMessage(servletRequest: HttpServletRequest, controllerPayload: Map<String, Any>): String {
|
||||
val payload = LogPayloadBuilder(type = LogType.CONTROLLER_INVOKED, servletRequest = servletRequest)
|
||||
.endpoint()
|
||||
.principalId()
|
||||
.additionalPayloads(controllerPayload)
|
||||
.build()
|
||||
|
||||
return objectMapper.writeValueAsString(payload)
|
||||
}
|
||||
|
||||
fun convertToResponseMessage(
|
||||
type: LogType,
|
||||
servletRequest: HttpServletRequest,
|
||||
httpStatusCode: Int,
|
||||
responseBody: Any? = null,
|
||||
exception: Exception? = null,
|
||||
): String {
|
||||
val payload = LogPayloadBuilder(type = type, servletRequest = servletRequest)
|
||||
.endpoint()
|
||||
.httpStatus(httpStatusCode)
|
||||
.durationMs()
|
||||
.principalId()
|
||||
.responseBody(responseBody)
|
||||
.exception(exception)
|
||||
.build()
|
||||
|
||||
return objectMapper.writeValueAsString(payload)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
package com.sangdol.common.web.support.log
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.sangdol.common.log.constant.LogType
|
||||
import com.sangdol.common.types.web.HttpStatus
|
||||
import com.sangdol.common.utils.MdcPrincipalIdUtil
|
||||
import com.sangdol.common.utils.MdcStartTimeUtil
|
||||
import io.kotest.assertions.assertSoftly
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
|
||||
class WebLogMessageConverterTest : FunSpec({
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
val converter = WebLogMessageConverter(objectMapper)
|
||||
val servletRequest: HttpServletRequest = mockk()
|
||||
|
||||
lateinit var method: String
|
||||
lateinit var requestUri: String
|
||||
lateinit var remoteAddr: String
|
||||
lateinit var userAgent: String
|
||||
lateinit var queryString: String
|
||||
|
||||
beforeTest {
|
||||
method = "GET".also { every { servletRequest.method } returns it }
|
||||
requestUri = "/converter/test".also { every { servletRequest.requestURI } returns it }
|
||||
remoteAddr = "localhost".also { every { servletRequest.remoteAddr } returns it }
|
||||
userAgent = "Mozilla/5.0".also { every { servletRequest.getHeader("User-Agent") } returns it }
|
||||
queryString = "key=value".also { every { servletRequest.queryString } returns it }
|
||||
}
|
||||
|
||||
afterSpec {
|
||||
clearMocks(servletRequest)
|
||||
}
|
||||
|
||||
context("Http 요청 메시지를 변환한다.") {
|
||||
test("정상 응답") {
|
||||
val result = converter.convertToHttpRequestMessage(servletRequest)
|
||||
|
||||
result shouldBe """
|
||||
{"type":"${LogType.INCOMING_HTTP_REQUEST.name}","endpoint":"$method $requestUri","query_params":"$queryString","client_ip":"$remoteAddr","user_agent":"$userAgent"}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
context("Controller 요청 메시지를 변환한다") {
|
||||
val principalId = 759980174446956066L.also {
|
||||
MdcPrincipalIdUtil.set(it.toString())
|
||||
}
|
||||
|
||||
test("정상 응답") {
|
||||
val controllerPayload: Map<String, Any> = mapOf(
|
||||
"controller_method" to "ThemeController.findThemeById(..)",
|
||||
"path_variable" to mapOf("id" to "7599801744469560667")
|
||||
)
|
||||
|
||||
val result = converter.convertToControllerInvokedMessage(servletRequest, controllerPayload)
|
||||
|
||||
result shouldBe """
|
||||
{"type":"${LogType.CONTROLLER_INVOKED.name}","endpoint":"$method $requestUri","principal_id":$principalId,"controller_method":"${controllerPayload["controller_method"]}","path_variable":{"id":"${7599801744469560667}"}}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
context("응답 메시지를 변환한다.") {
|
||||
val principalId = 7599801744469560666
|
||||
|
||||
val body = mapOf(
|
||||
"id" to 7599801744469560667,
|
||||
"name" to "sangdol"
|
||||
)
|
||||
|
||||
val exception = RuntimeException("hello")
|
||||
|
||||
beforeTest {
|
||||
MdcPrincipalIdUtil.set(principalId.toString())
|
||||
MdcStartTimeUtil.setCurrentTime()
|
||||
}
|
||||
|
||||
afterTest {
|
||||
MdcPrincipalIdUtil.clear()
|
||||
MdcStartTimeUtil.clear()
|
||||
}
|
||||
|
||||
test("응답 본문을 포함한다.") {
|
||||
val result = converter.convertToResponseMessage(
|
||||
type = LogType.SUCCEED,
|
||||
servletRequest = servletRequest,
|
||||
httpStatusCode = HttpStatus.OK.value(),
|
||||
responseBody = body
|
||||
)
|
||||
|
||||
assertSoftly(objectMapper.readValue(result, LinkedHashMap::class.java)) {
|
||||
this["type"] shouldBe LogType.SUCCEED.name
|
||||
this["endpoint"] shouldBe "$method $requestUri"
|
||||
this["status_code"] shouldBe HttpStatus.OK.value()
|
||||
this["duration_ms"].shouldNotBeNull()
|
||||
this["principal_id"] shouldBe principalId
|
||||
this["response_body"] shouldBe body
|
||||
this["exception"] shouldBe null
|
||||
}
|
||||
}
|
||||
|
||||
test("예외를 포함한다.") {
|
||||
val result = converter.convertToResponseMessage(
|
||||
type = LogType.SUCCEED,
|
||||
servletRequest = servletRequest,
|
||||
httpStatusCode = HttpStatus.OK.value(),
|
||||
exception = exception
|
||||
)
|
||||
|
||||
assertSoftly(objectMapper.readValue(result, LinkedHashMap::class.java)) {
|
||||
this["type"] shouldBe LogType.SUCCEED.name
|
||||
this["endpoint"] shouldBe "$method $requestUri"
|
||||
this["status_code"] shouldBe HttpStatus.OK.value()
|
||||
this["duration_ms"].shouldNotBeNull()
|
||||
this["principal_id"] shouldBe principalId
|
||||
this["response_body"] shouldBe null
|
||||
this["exception"] shouldBe mapOf("class" to exception.javaClass.simpleName, "message" to exception.message)
|
||||
}
|
||||
}
|
||||
|
||||
test("예외 + 응답 본문을 모두 포함한다.") {
|
||||
val result = converter.convertToResponseMessage(
|
||||
type = LogType.SUCCEED,
|
||||
servletRequest = servletRequest,
|
||||
httpStatusCode = HttpStatus.OK.value(),
|
||||
responseBody = body,
|
||||
exception = exception
|
||||
)
|
||||
|
||||
assertSoftly(objectMapper.readValue(result, LinkedHashMap::class.java)) {
|
||||
this["type"] shouldBe LogType.SUCCEED.name
|
||||
this["endpoint"] shouldBe "$method $requestUri"
|
||||
this["status_code"] shouldBe HttpStatus.OK.value()
|
||||
this["duration_ms"].shouldNotBeNull()
|
||||
this["principal_id"] shouldBe principalId
|
||||
this["response_body"] shouldBe body
|
||||
this["exception"] shouldBe mapOf("class" to exception.javaClass.simpleName, "message" to exception.message)
|
||||
}
|
||||
}
|
||||
|
||||
test("예외, 응답 본문 모두 제외한다.") {
|
||||
val result = converter.convertToResponseMessage(
|
||||
type = LogType.SUCCEED,
|
||||
servletRequest = servletRequest,
|
||||
httpStatusCode = HttpStatus.OK.value(),
|
||||
)
|
||||
|
||||
assertSoftly(objectMapper.readValue(result, LinkedHashMap::class.java)) {
|
||||
this["type"] shouldBe LogType.SUCCEED.name
|
||||
this["endpoint"] shouldBe "$method $requestUri"
|
||||
this["status_code"] shouldBe HttpStatus.OK.value()
|
||||
this["duration_ms"].shouldNotBeNull()
|
||||
this["principal_id"] shouldBe principalId
|
||||
this["response_body"] shouldBe null
|
||||
this["exception"] shouldBe null
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user