feat(ranking): 스냅샷 job 상태 로그를 추가한다
This commit is contained in:
@@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobPor
|
|||||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobRecord
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobRecord
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobStatus
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobStatus
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobTrigger
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobTrigger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -17,6 +18,7 @@ class CreatorRankingSnapshotJobService(
|
|||||||
private val jobPort: CreatorRankingSnapshotJobPort,
|
private val jobPort: CreatorRankingSnapshotJobPort,
|
||||||
private val nowProvider: () -> ZonedDateTime = { ZonedDateTime.now() }
|
private val nowProvider: () -> ZonedDateTime = { ZonedDateTime.now() }
|
||||||
) {
|
) {
|
||||||
|
private val log = LoggerFactory.getLogger(javaClass)
|
||||||
private val periodPolicy = CreatorRankingPeriodPolicy()
|
private val periodPolicy = CreatorRankingPeriodPolicy()
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -37,11 +39,14 @@ class CreatorRankingSnapshotJobService(
|
|||||||
)
|
)
|
||||||
val jobId = job.id ?: return
|
val jobId = job.id ?: return
|
||||||
jobPort.markProcessing(jobId, LocalDateTime.now())
|
jobPort.markProcessing(jobId, LocalDateTime.now())
|
||||||
|
logJobStatusChanged(job, CreatorRankingSnapshotJobStatus.PROCESSING)
|
||||||
try {
|
try {
|
||||||
refreshService.refreshLastCompletedWeek(now)
|
refreshService.refreshLastCompletedWeek(now)
|
||||||
jobPort.markDone(jobId, LocalDateTime.now())
|
jobPort.markDone(jobId, LocalDateTime.now())
|
||||||
|
logJobStatusChanged(job, CreatorRankingSnapshotJobStatus.DONE)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
jobPort.markFailed(jobId, LocalDateTime.now(), ex.message)
|
jobPort.markFailed(jobId, LocalDateTime.now(), ex.message)
|
||||||
|
logJobStatusChanged(job, CreatorRankingSnapshotJobStatus.FAILED, ex.message)
|
||||||
throw ex
|
throw ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,4 +88,21 @@ class CreatorRankingSnapshotJobService(
|
|||||||
|
|
||||||
jobPort.markPending(jobId)
|
jobPort.markPending(jobId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun logJobStatusChanged(
|
||||||
|
job: CreatorRankingSnapshotJobRecord,
|
||||||
|
status: CreatorRankingSnapshotJobStatus,
|
||||||
|
error: String? = null
|
||||||
|
) {
|
||||||
|
log.info(
|
||||||
|
"event=creator_ranking_snapshot_job_status_changed " +
|
||||||
|
"jobId={} trigger={} status={} aggregationStartAtUtc={} aggregationEndAtUtc={} error={}",
|
||||||
|
job.id,
|
||||||
|
job.trigger,
|
||||||
|
status,
|
||||||
|
job.aggregationStartAtUtc,
|
||||||
|
job.aggregationEndAtUtc,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,18 @@ import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobSta
|
|||||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobTrigger
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobTrigger
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
|
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.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.boot.test.system.CapturedOutput
|
||||||
|
import org.springframework.boot.test.system.OutputCaptureExtension
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
@ExtendWith(OutputCaptureExtension::class)
|
||||||
class CreatorRankingSnapshotJobServiceTest {
|
class CreatorRankingSnapshotJobServiceTest {
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("스케줄 실행은 집계 기간을 포함한 SCHEDULED job을 생성하고 성공 시 DONE으로 기록한다")
|
@DisplayName("스케줄 실행은 집계 기간을 포함한 SCHEDULED job을 생성하고 성공 시 DONE으로 기록한다")
|
||||||
@@ -154,6 +159,44 @@ class CreatorRankingSnapshotJobServiceTest {
|
|||||||
assertEquals(CreatorRankingSnapshotJobStatus.PENDING, unchanged.status)
|
assertEquals(CreatorRankingSnapshotJobStatus.PENDING, unchanged.status)
|
||||||
assertEquals("keep", unchanged.lastError)
|
assertEquals("keep", unchanged.lastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("스케줄 job 상태 변경은 job id와 상태를 로그로 남긴다")
|
||||||
|
fun shouldLogScheduledJobStatusTransitions(output: CapturedOutput) {
|
||||||
|
val refreshService = Mockito.mock(CreatorRankingSnapshotRefreshService::class.java)
|
||||||
|
val jobPort = FakeCreatorRankingSnapshotJobPort()
|
||||||
|
val now = ZonedDateTime.of(2026, 6, 8, 7, 30, 0, 0, ZoneId.of("Asia/Seoul"))
|
||||||
|
val service = CreatorRankingSnapshotJobService(refreshService, jobPort) { now }
|
||||||
|
|
||||||
|
service.refreshLastCompletedWeekByScheduledJob()
|
||||||
|
|
||||||
|
assertTrue(output.out.contains("event=creator_ranking_snapshot_job_status_changed"))
|
||||||
|
assertTrue(output.out.contains("jobId=1"))
|
||||||
|
assertTrue(output.out.contains("trigger=SCHEDULED"))
|
||||||
|
assertTrue(output.out.contains("status=PROCESSING"))
|
||||||
|
assertTrue(output.out.contains("status=DONE"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("실패 job 상태 변경은 실패 상태와 사유를 로그로 남긴다")
|
||||||
|
fun shouldLogFailedScheduledJobStatusTransition(output: CapturedOutput) {
|
||||||
|
val refreshService = Mockito.mock(CreatorRankingSnapshotRefreshService::class.java)
|
||||||
|
val jobPort = FakeCreatorRankingSnapshotJobPort()
|
||||||
|
val now = ZonedDateTime.of(2026, 6, 8, 7, 30, 0, 0, ZoneId.of("Asia/Seoul"))
|
||||||
|
val service = CreatorRankingSnapshotJobService(refreshService, jobPort) { now }
|
||||||
|
Mockito.doThrow(IllegalStateException("aggregate failed"))
|
||||||
|
.`when`(refreshService).refreshLastCompletedWeek(now)
|
||||||
|
|
||||||
|
assertThrows(IllegalStateException::class.java) {
|
||||||
|
service.refreshLastCompletedWeekByScheduledJob()
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(output.out.contains("event=creator_ranking_snapshot_job_status_changed"))
|
||||||
|
assertTrue(output.out.contains("jobId=1"))
|
||||||
|
assertTrue(output.out.contains("trigger=SCHEDULED"))
|
||||||
|
assertTrue(output.out.contains("status=FAILED"))
|
||||||
|
assertTrue(output.out.contains("error=aggregate failed"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FakeCreatorRankingSnapshotJobPort : CreatorRankingSnapshotJobPort {
|
private class FakeCreatorRankingSnapshotJobPort : CreatorRankingSnapshotJobPort {
|
||||||
|
|||||||
Reference in New Issue
Block a user