From 33406fbc9383705d863f616792a6085be0379c4e Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 27 Sep 2025 22:31:56 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EA=B8=B0=EC=A1=B4=20log=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=9D=BC=EB=B6=80?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=8A=94=20=EC=9E=AC=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=84=B1=20=EA=B3=A0=EB=A0=A4=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../log/AbstractLogMaskingConverterTest.kt | 55 +++++++++++++ .../common/log/ApiLogMessageConverterTest.kt | 15 ++-- ...AwareSlowQueryListenerWithoutParamsTest.kt | 28 +++++++ .../log/RoomescapeLogMaskingConverterTest.kt | 77 ------------------- 4 files changed, 91 insertions(+), 84 deletions(-) create mode 100644 common/log/src/test/kotlin/com/sangdol/common/log/AbstractLogMaskingConverterTest.kt rename {service/src/test/kotlin/com/sangdol/roomescape => common/log/src/test/kotlin/com/sangdol}/common/log/ApiLogMessageConverterTest.kt (82%) create mode 100644 common/log/src/test/kotlin/com/sangdol/common/log/MDCAwareSlowQueryListenerWithoutParamsTest.kt delete mode 100644 service/src/test/kotlin/com/sangdol/roomescape/common/log/RoomescapeLogMaskingConverterTest.kt diff --git a/common/log/src/test/kotlin/com/sangdol/common/log/AbstractLogMaskingConverterTest.kt b/common/log/src/test/kotlin/com/sangdol/common/log/AbstractLogMaskingConverterTest.kt new file mode 100644 index 00000000..eeb74b46 --- /dev/null +++ b/common/log/src/test/kotlin/com/sangdol/common/log/AbstractLogMaskingConverterTest.kt @@ -0,0 +1,55 @@ +package com.sangdol.common.log + +import ch.qos.logback.classic.spi.ILoggingEvent +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.sangdol.common.log.message.AbstractLogMaskingConverter +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.equals.shouldBeEqual +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.mockk.every +import io.mockk.mockk + +class TestLogMaskingConverter : AbstractLogMaskingConverter( + sensitiveKeys = setOf("account", "address"), + objectMapper = jacksonObjectMapper() +) + +class AbstractLogMaskingConverterTest : FunSpec({ + + val converter = TestLogMaskingConverter() + val event: ILoggingEvent = mockk() + val account = "sangdol@example.com" + val address = "서울특별시 강북구 수유1동 123-456" + + context("sensitiveKeys=${converter.sensitiveKeys}에 있는 항목은 가린다.") { + context("평문 로그를 처리할 때, 여러 key / value가 있는 경우 서로 간의 구분자는 trim 처리한다.") { + listOf(":", "=", " : ", " = ").forEach { keyValueDelimiter -> + listOf(",", ", ").forEach { valueDelimiter -> + test("key1${keyValueDelimiter}value1${valueDelimiter}key2${keyValueDelimiter}value2 형식을 처리한다.") { + every { + event.formattedMessage + } returns "account$keyValueDelimiter$account${valueDelimiter}address$keyValueDelimiter$address" + + assertSoftly(converter.convert(event)) { + this shouldBe "account${keyValueDelimiter}${account.first()}${converter.mask}${account.last()}${valueDelimiter}address${keyValueDelimiter}${address.first()}${converter.mask}${address.last()}" + } + } + } + } + } + + context("JSON 로그") { + test("정상 처리") { + val json = "{\"request_body\":{\"account\":\"%s\",\"address\":\"%s\"}}" + + every { + event.formattedMessage + } returns json.format(account, address) + + converter.convert(event) shouldBeEqual json.format("${account.first()}${converter.mask}${account.last()}", "${address.first()}${converter.mask}${address.last()}") + } + } + } +}) diff --git a/service/src/test/kotlin/com/sangdol/roomescape/common/log/ApiLogMessageConverterTest.kt b/common/log/src/test/kotlin/com/sangdol/common/log/ApiLogMessageConverterTest.kt similarity index 82% rename from service/src/test/kotlin/com/sangdol/roomescape/common/log/ApiLogMessageConverterTest.kt rename to common/log/src/test/kotlin/com/sangdol/common/log/ApiLogMessageConverterTest.kt index cc8eec20..8e76c8d0 100644 --- a/service/src/test/kotlin/com/sangdol/roomescape/common/log/ApiLogMessageConverterTest.kt +++ b/common/log/src/test/kotlin/com/sangdol/common/log/ApiLogMessageConverterTest.kt @@ -1,8 +1,9 @@ -package com.sangdol.roomescape.common.log +package com.sangdol.common.log -import com.sangdol.common.config.JacksonConfig -import com.sangdol.roomescape.auth.exception.AuthErrorCode -import com.sangdol.roomescape.auth.exception.AuthException +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 @@ -11,7 +12,7 @@ import jakarta.servlet.http.HttpServletRequest import org.slf4j.MDC class ApiLogMessageConverterTest : StringSpec({ - val converter = ApiLogMessageConverter(JacksonConfig().objectMapper()) + val converter = ApiLogMessageConverter(jacksonObjectMapper()) val request: HttpServletRequest = mockk() beforeTest { @@ -58,11 +59,11 @@ class ApiLogMessageConverterTest : StringSpec({ type = LogType.CONTROLLER_SUCCESS, endpoint = endpoint, httpStatus = 200, - exception = AuthException(AuthErrorCode.MEMBER_NOT_FOUND, "테스트 메시지!") + exception = RuntimeException("테스트 메시지!") ) converter.convertToResponseMessage(request) shouldBe """ - {"type":"CONTROLLER_SUCCESS","endpoint":"$endpoint","status_code":200,"principal_id":1,"exception":{"class":"AuthException","message":"테스트 메시지!"}} + {"type":"CONTROLLER_SUCCESS","endpoint":"$endpoint","status_code":200,"principal_id":1,"exception":{"class":"RuntimeException","message":"테스트 메시지!"}} """.trimIndent() } }) diff --git a/common/log/src/test/kotlin/com/sangdol/common/log/MDCAwareSlowQueryListenerWithoutParamsTest.kt b/common/log/src/test/kotlin/com/sangdol/common/log/MDCAwareSlowQueryListenerWithoutParamsTest.kt new file mode 100644 index 00000000..2d160784 --- /dev/null +++ b/common/log/src/test/kotlin/com/sangdol/common/log/MDCAwareSlowQueryListenerWithoutParamsTest.kt @@ -0,0 +1,28 @@ +package com.sangdol.common.log + +import com.sangdol.common.log.sql.SlowQueryPredicate +import com.sangdol.common.log.sql.SqlLogFormatter +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +class MDCAwareSlowQueryListenerWithoutParamsTest : StringSpec({ + "SQL 메시지에서 Params 항목은 가린다." { + val message = """Query:["select * from members m where m.email=?"], Params:[(a@a.a)]""" + val expected = """Query:["select * from members m where m.email=?"]""" + val result = SqlLogFormatter().maskParams(message) + + result shouldBe expected + } + + "입력된 thresholdMs 보다 소요시간이 긴 쿼리를 기록한다." { + val slowQueryThreshold = 10L + val slowQueryPredicate = SlowQueryPredicate(thresholdMs = slowQueryThreshold) + + assertSoftly(slowQueryPredicate) { + this.test(slowQueryThreshold) shouldBe true + this.test(slowQueryThreshold + 1) shouldBe true + this.test(slowQueryThreshold - 1) shouldBe false + } + } +}) diff --git a/service/src/test/kotlin/com/sangdol/roomescape/common/log/RoomescapeLogMaskingConverterTest.kt b/service/src/test/kotlin/com/sangdol/roomescape/common/log/RoomescapeLogMaskingConverterTest.kt deleted file mode 100644 index 2db3f5f7..00000000 --- a/service/src/test/kotlin/com/sangdol/roomescape/common/log/RoomescapeLogMaskingConverterTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.sangdol.roomescape.common.log - -import ch.qos.logback.classic.spi.ILoggingEvent -import io.kotest.assertions.assertSoftly -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.equals.shouldBeEqual -import io.kotest.matchers.string.shouldContain -import io.mockk.every -import io.mockk.mockk - -class RoomescapeLogMaskingConverterTest : FunSpec({ - - val converter = RoomescapeLogMaskingConverter() - val event: ILoggingEvent = mockk() - - context("평문 로그에서는 key=value 형식을 처리한다.") { - - test("2글자 초과이면 맨 앞, 맨 뒤를 제외한 나머지를 가린다.") { - val email = "a@a.a" - val password = "password12" - val accessToken = "accessToken12" - - every { - event.formattedMessage - } returns "email=${email}, password=${password}, accessToken = $accessToken" - - assertSoftly(converter.convert(event)) { - this shouldContain "email=${email}" - this shouldContain "password=${password.first()}****${password.last()}" - this shouldContain "accessToken = ${accessToken.first()}****${accessToken.last()}" - } - - } - - test("2글자 이하이면 전부 가린다.") { - val email = "a@a.a" - val password = "pa" - val accessToken = "a" - - every { - event.formattedMessage - } returns "email=${email}, password=${password}, accessToken = ${accessToken}" - - assertSoftly(converter.convert(event)) { - this shouldContain "email=${email}" - this shouldContain "password=****" - this shouldContain "accessToken = ****" - } - } - } - - context("JSON 형식 로그를 처리한다.") { - val json = "{\"request_body\":{\"email\":\"a@a.a\",\"password\":\"password12\"}}" - - test("2글자 초과이면 맨 앞, 맨 뒤를 제외한 나머지를 가린다.") { - val password = "password12" - val json = "{\"request_body\":{\"email\":\"a@a.a\",\"password\":\"${password}\"}}" - - every { - event.formattedMessage - } returns json - - converter.convert(event) shouldBeEqual "{\"request_body\":{\"email\":\"a@a.a\",\"password\":\"${password.first()}****${password.last()}\"}}" - } - - test("2글자 이하이면 전부 가린다.") { - val password = "pa" - val json = "{\"request_body\":{\"email\":\"a@a.a\",\"password\":\"${password}\"}}" - - every { - event.formattedMessage - } returns json - - converter.convert(event) shouldBeEqual "{\"request_body\":{\"email\":\"a@a.a\",\"password\":\"****\"}}" - } - } -})