feat(ranking): 랭킹 조회 관측 로그를 추가한다

This commit is contained in:
2026-06-09 00:09:17 +09:00
parent 5f08165239
commit 394786e6bc
2 changed files with 89 additions and 17 deletions

View File

@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingItem
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingBlockPort import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingBlockPort
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotPort import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotPort
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
@@ -15,11 +16,18 @@ class CreatorRankingQueryService(
@Value("\${cloud.aws.cloud-front.host}") @Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String private val cloudFrontHost: String
) { ) {
private val log = LoggerFactory.getLogger(javaClass)
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun getCreatorRankings(viewerMemberId: Long?): CreatorRankingResult { fun getCreatorRankings(viewerMemberId: Long?): CreatorRankingResult {
val startedAt = System.currentTimeMillis()
return runCatching {
val latestItems = snapshotPort.findLatestSnapshots().toRankedItems() val latestItems = snapshotPort.findLatestSnapshots().toRankedItems()
if (latestItems.isEmpty()) { if (latestItems.isEmpty()) {
return CreatorRankingResult(showRankChange = false, items = emptyList()) return@runCatching QueryLogResult(
result = CreatorRankingResult(showRankChange = false, items = emptyList()),
blockedCreatorCount = 0
)
} }
val previousItems = snapshotPort.findPreviousCompletedSnapshots().toRankedItems() val previousItems = snapshotPort.findPreviousCompletedSnapshots().toRankedItems()
@@ -34,9 +42,33 @@ class CreatorRankingQueryService(
).maskIfBlocked(blockedCreatorIds) ).maskIfBlocked(blockedCreatorIds)
} }
return CreatorRankingResult(showRankChange = showRankChange, items = items) QueryLogResult(
result = CreatorRankingResult(showRankChange = showRankChange, items = items),
blockedCreatorCount = blockedCreatorIds.size
)
}.onSuccess { logResult ->
log.info(
"event=creator_ranking_query_success showRankChange={} itemCount={} blockedCreatorCount={} elapsedMs={}",
logResult.result.showRankChange,
logResult.result.items.size,
logResult.blockedCreatorCount,
System.currentTimeMillis() - startedAt
)
}.onFailure { ex ->
log.warn(
"event=creator_ranking_query_failure elapsedMs={} error={}",
System.currentTimeMillis() - startedAt,
ex.message,
ex
)
}.getOrThrow().result
} }
private data class QueryLogResult(
val result: CreatorRankingResult,
val blockedCreatorCount: Int
)
private fun List<CreatorRankingSnapshotRecord>.toRankedItems(): List<CreatorRankingItem> { private fun List<CreatorRankingSnapshotRecord>.toRankedItems(): List<CreatorRankingItem> {
return groupBy { it.finalScore } return groupBy { it.finalScore }
.toSortedMap(compareByDescending { it }) .toSortedMap(compareByDescending { it })

View File

@@ -8,11 +8,16 @@ import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.system.CapturedOutput
import org.springframework.boot.test.system.OutputCaptureExtension
import java.time.LocalDateTime import java.time.LocalDateTime
@ExtendWith(OutputCaptureExtension::class)
class CreatorRankingQueryServiceTest { class CreatorRankingQueryServiceTest {
@Test @Test
@DisplayName("스냅샷 후보와 조회 item 내부 모델은 순위 변화와 신규 진입 값을 담을 수 있다") @DisplayName("스냅샷 후보와 조회 item 내부 모델은 순위 변화와 신규 진입 값을 담을 수 있다")
@@ -177,6 +182,37 @@ class CreatorRankingQueryServiceTest {
assertEquals("profile-1.png", result.items.single().profileImageUrl) assertEquals("profile-1.png", result.items.single().profileImageUrl)
} }
@Test
@DisplayName("크리에이터 랭킹 조회 성공은 순위 변화 노출 여부와 반환 수를 로그로 남긴다")
fun shouldLogCreatorRankingQuerySuccessWithResultCounts(output: CapturedOutput) {
val snapshotPort = FakeCreatorRankingQuerySnapshotPort()
snapshotPort.latestSnapshots = listOf(snapshot(creatorId = 1L, finalScore = 100.0))
val service = service(snapshotPort = snapshotPort)
service.getCreatorRankings(viewerMemberId = null)
assertTrue(output.out.contains("event=creator_ranking_query_success"))
assertTrue(output.out.contains("showRankChange=false"))
assertTrue(output.out.contains("itemCount=1"))
assertTrue(output.out.contains("blockedCreatorCount=0"))
}
@Test
@DisplayName("크리에이터 랭킹 조회 실패는 에러를 로그로 남기고 예외를 전파한다")
fun shouldLogCreatorRankingQueryFailureWithError(output: CapturedOutput) {
val snapshotPort = FakeCreatorRankingQuerySnapshotPort()
snapshotPort.latestFailure = IllegalStateException("latest snapshots failed")
val service = service(snapshotPort = snapshotPort)
val exception = assertThrows(IllegalStateException::class.java) {
service.getCreatorRankings(viewerMemberId = 99L)
}
assertEquals("latest snapshots failed", exception.message)
assertTrue(output.out.contains("event=creator_ranking_query_failure"))
assertTrue(output.out.contains("error=latest snapshots failed"))
}
private fun service( private fun service(
snapshotPort: CreatorRankingSnapshotPort = FakeCreatorRankingQuerySnapshotPort(), snapshotPort: CreatorRankingSnapshotPort = FakeCreatorRankingQuerySnapshotPort(),
blockPort: CreatorRankingBlockPort = FakeCreatorRankingBlockPort() blockPort: CreatorRankingBlockPort = FakeCreatorRankingBlockPort()
@@ -219,13 +255,17 @@ class CreatorRankingQueryServiceTest {
private class FakeCreatorRankingQuerySnapshotPort : CreatorRankingSnapshotPort { private class FakeCreatorRankingQuerySnapshotPort : CreatorRankingSnapshotPort {
var latestSnapshots: List<CreatorRankingSnapshotRecord> = emptyList() var latestSnapshots: List<CreatorRankingSnapshotRecord> = emptyList()
var previousSnapshots: List<CreatorRankingSnapshotRecord> = emptyList() var previousSnapshots: List<CreatorRankingSnapshotRecord> = emptyList()
var latestFailure: RuntimeException? = null
override fun findSnapshotsByAggregationPeriod( override fun findSnapshotsByAggregationPeriod(
aggregationStartAtUtc: LocalDateTime, aggregationStartAtUtc: LocalDateTime,
aggregationEndAtUtc: LocalDateTime aggregationEndAtUtc: LocalDateTime
): List<CreatorRankingSnapshotRecord> = emptyList() ): List<CreatorRankingSnapshotRecord> = emptyList()
override fun findLatestSnapshots(): List<CreatorRankingSnapshotRecord> = latestSnapshots override fun findLatestSnapshots(): List<CreatorRankingSnapshotRecord> {
latestFailure?.let { throw it }
return latestSnapshots
}
override fun findPreviousCompletedSnapshots(): List<CreatorRankingSnapshotRecord> = previousSnapshots override fun findPreviousCompletedSnapshots(): List<CreatorRankingSnapshotRecord> = previousSnapshots