Skip to content

Commit

Permalink
feat: event 처리, 비동기 처리용 CoroutineScope에 coroutine exception handler 추가 (
Browse files Browse the repository at this point in the history
#67)

* feat: error slack publish하는 exception handler 배치 잡 context에 추가

* feat: event, 비동기 처리위한 CoroutineScope에 exception handler 추가

* chore: ktlint format

* fix: 단위 테스트에 coroutineExcpetionHandler mock 추가
  • Loading branch information
wjdtkdgns authored Jul 15, 2024
1 parent 2ae4328 commit dbf1f42
Show file tree
Hide file tree
Showing 29 changed files with 172 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.oksusu.susu.api.category.application

import com.oksusu.susu.api.category.model.CategoryModel
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.exception.ErrorCode
import com.oksusu.susu.common.exception.NotFoundException
import com.oksusu.susu.common.extension.resolveCancellation
Expand All @@ -18,6 +19,7 @@ import org.springframework.stereotype.Service
@Service
class CategoryService(
private val categoryRepository: CategoryRepository,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
private val logger = KotlinLogging.logger { }
private var categories: Map<Long, CategoryModel> = emptyMap()
Expand All @@ -27,7 +29,7 @@ class CategoryService(
initialDelayString = "\${oksusu.scheduled-tasks.refresh-categories.initial-delay:0}"
)
fun refreshCategories() {
CoroutineScope(Dispatchers.IO + Job()).launch {
CoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler).launch {
logger.info { "start refresh categories" }

categories = runCatching {
Expand Down
20 changes: 11 additions & 9 deletions api/src/main/kotlin/com/oksusu/susu/api/dev/DevBatchResource.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.oksusu.susu.batch.report.job.ImposeSanctionsAboutReportJob
import com.oksusu.susu.batch.summary.job.SusuStatisticsDailySummaryJob
import com.oksusu.susu.batch.summary.job.SusuStatisticsHourSummaryJob
import com.oksusu.susu.batch.user.job.DeleteWithdrawUserDataJob
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import kotlinx.coroutines.CoroutineScope
Expand All @@ -26,13 +27,14 @@ class DevBatchResource(
private val susuEnvelopeStatisticJob: RefreshSusuEnvelopeStatisticJob,
private val deleteWithdrawUserDataJob: DeleteWithdrawUserDataJob,
private val imposeSanctionsAboutReportJob: ImposeSanctionsAboutReportJob,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
@Operation(tags = [SwaggerTag.DEV_SWAGGER_TAG], summary = "hour summary 호출")
@GetMapping("/hour-summaries")
suspend fun getHourSummaries(
adminUser: AdminUser,
) {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch {
susuStatisticsHourSummaryJob.runHourSummaryJob()
}
}
Expand All @@ -42,7 +44,7 @@ class DevBatchResource(
suspend fun getDailySummaries(
adminUser: AdminUser,
) {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch {
susuStatisticsDailySummaryJob.runDailySummaryJob()
}
}
Expand All @@ -52,7 +54,7 @@ class DevBatchResource(
suspend fun refreshSusuEnvelopeStatistic(
adminUser: AdminUser,
) {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch {
susuEnvelopeStatisticJob.refreshSusuEnvelopeStatistic()
}
}
Expand All @@ -63,7 +65,7 @@ class DevBatchResource(
suspend fun deleteWithdrawUserData(
adminUser: AdminUser,
) {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch {
deleteWithdrawUserDataJob.deleteWithdrawUserData()
}
}
Expand All @@ -73,7 +75,7 @@ class DevBatchResource(
suspend fun deleteWithdrawUserDataForWeek(
adminUser: AdminUser,
) {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch {
deleteWithdrawUserDataJob.deleteWithdrawUserDataForWeek()
}
}
Expand All @@ -83,7 +85,7 @@ class DevBatchResource(
suspend fun imposeSanctionsAboutReportForDay(
adminUser: AdminUser,
) {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch {
imposeSanctionsAboutReportJob.imposeSanctionsAboutReportForDay()
}
}
Expand All @@ -94,7 +96,7 @@ class DevBatchResource(
suspend fun updateReportCount(
adminUser: AdminUser,
) {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch {
imposeSanctionsAboutReportJob.updateReportCount()
}
}
Expand All @@ -105,7 +107,7 @@ class DevBatchResource(
suspend fun updateUserCommunityPunishCount(
adminUser: AdminUser,
) {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch {
imposeSanctionsAboutReportJob.updateUserCommunityPunishCount()
}
}
Expand All @@ -116,7 +118,7 @@ class DevBatchResource(
suspend fun refreshSusuEnvelopeStatisticAmount(
adminUser: AdminUser,
) {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch {
susuEnvelopeStatisticJob.refreshSusuEnvelopeStatisticAmount()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.oksusu.susu.api.common.aspect.SusuEventListener
import com.oksusu.susu.api.event.model.CacheAppleOidcPublicKeysEvent
import com.oksusu.susu.cache.key.Cache
import com.oksusu.susu.cache.service.CacheService
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.extension.mdcCoroutineScope
import com.oksusu.susu.common.extension.withMDCContext
import io.github.oshai.kotlinlogging.KotlinLogging
Expand All @@ -15,12 +16,13 @@ import org.springframework.transaction.event.TransactionalEventListener
@SusuEventListener
class AuthEventListener(
private val cacheService: CacheService,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
private val logger = KotlinLogging.logger { }

@TransactionalEventListener
fun cacheAppleOidcPublicKeysService(event: CacheAppleOidcPublicKeysEvent) {
mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch {
mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch {
logger.info { "[${event.publishAt}] apple oidc pub key 캐싱 시작" }

withMDCContext(Dispatchers.IO) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.oksusu.susu.api.event.listener
import com.oksusu.susu.api.common.aspect.SusuEventListener
import com.oksusu.susu.api.count.application.CountService
import com.oksusu.susu.api.event.model.DeleteVoteCountEvent
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.extension.mdcCoroutineScope
import com.oksusu.susu.domain.common.extension.coExecuteOrNull
import com.oksusu.susu.domain.config.database.TransactionTemplates
Expand All @@ -17,12 +18,13 @@ import org.springframework.transaction.event.TransactionalEventListener
class CountEventListener(
private val countService: CountService,
private val txTemplates: TransactionTemplates,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
private val logger = KotlinLogging.logger { }

@TransactionalEventListener
fun deleteCount(event: DeleteVoteCountEvent) {
mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch {
mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch {
logger.info { "[${event.publishAt}] ${event.postId} post 관련 count delete 시작" }

txTemplates.writer.coExecuteOrNull {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.oksusu.susu.api.envelope.application.EnvelopeService
import com.oksusu.susu.api.event.model.DeleteEnvelopeEvent
import com.oksusu.susu.api.friend.application.FriendRelationshipService
import com.oksusu.susu.api.friend.application.FriendService
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.extension.mdcCoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
Expand All @@ -16,10 +17,11 @@ class EnvelopeEventListener(
private val envelopeService: EnvelopeService,
private val friendService: FriendService,
private val friendRelationshipService: FriendRelationshipService,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
@TransactionalEventListener
fun handel(event: DeleteEnvelopeEvent) {
mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch {
mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch {
val count = envelopeService.countByUidAndFriendId(
uid = event.uid,
friendId = event.friendId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.oksusu.susu.api.category.application.CategoryAssignmentService
import com.oksusu.susu.api.common.aspect.SusuEventListener
import com.oksusu.susu.api.envelope.application.EnvelopeService
import com.oksusu.susu.api.event.model.DeleteLedgerEvent
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.extension.mdcCoroutineScope
import com.oksusu.susu.domain.category.domain.vo.CategoryAssignmentType
import com.oksusu.susu.domain.common.extension.coExecuteOrNull
Expand All @@ -18,10 +19,11 @@ class LedgerEventListener(
private val envelopeService: EnvelopeService,
private val categoryAssignmentService: CategoryAssignmentService,
private val txTemplates: TransactionTemplates,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
@TransactionalEventListener
fun handel(event: DeleteLedgerEvent) {
mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch {
mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch {
val envelopes = envelopeService.findAllByLedgerId(event.ledger.id)
val envelopeIds = envelopes.map { envelope -> envelope.id }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.oksusu.susu.api.event.listener
import com.oksusu.susu.api.common.aspect.SusuEventListener
import com.oksusu.susu.api.event.model.SentryCaptureExceptionEvent
import com.oksusu.susu.api.extension.remoteIp
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.extension.isProd
import com.oksusu.susu.common.extension.mdcCoroutineScope
import io.github.oshai.kotlinlogging.KotlinLogging
Expand All @@ -19,6 +20,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest
@SusuEventListener
class SentryCaptureExceptionEventListener(
private val environment: Environment,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
private val logger = KotlinLogging.logger { }

Expand All @@ -29,7 +31,7 @@ class SentryCaptureExceptionEventListener(
return
}

mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch {
mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch {
val throwable = event.exception
val request = event.request

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@ package com.oksusu.susu.api.event.listener
import com.oksusu.susu.api.common.aspect.SusuEventListener
import com.oksusu.susu.api.event.model.SlackErrorAlarmEvent
import com.oksusu.susu.api.extension.remoteIp
import com.oksusu.susu.api.extension.requestParam
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.client.slack.SlackClient
import com.oksusu.susu.client.slack.model.SlackMessageModel
import com.oksusu.susu.common.extension.format
import com.oksusu.susu.common.extension.isProd
import com.oksusu.susu.common.extension.mdcCoroutineScope
import com.oksusu.susu.common.extension.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.reactor.awaitSingleOrNull
import org.springframework.context.event.EventListener
import org.springframework.core.env.Environment
import org.springframework.core.io.buffer.DataBufferUtils
import org.springframework.http.server.reactive.ServerHttpRequest
import java.time.LocalDateTime

@SusuEventListener
class SlackErrorAlarmEventListener(
private val environment: Environment,
private val slackClient: SlackClient,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
@EventListener
fun execute(event: SlackErrorAlarmEvent) {
Expand All @@ -30,13 +30,13 @@ class SlackErrorAlarmEventListener(
return
}

mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch {
mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch {
val url = event.request.uri.toString()
val method = event.request.method.toString()
val errorMessage = event.exception.toString()
val errorStack = getErrorStack(event.exception)
val errorStack = event.exception.supressedErrorStack
val errorUserIP = event.request.remoteIp
val errorRequestParam = getRequestParam(event.request)
val errorRequestParam = event.request.requestParam
val body = DataBufferUtils.join(event.request.body)
.map { dataBuffer ->
val bytes = ByteArray(dataBuffer.readableByteCount())
Expand All @@ -56,27 +56,6 @@ class SlackErrorAlarmEventListener(
).run { slackClient.sendError(this.message()) }
}
}

private fun getErrorStack(e: Exception): String {
val exceptionAsStrings = e.suppressedExceptions.flatMap { exception ->
exception.stackTrace.map { stackTrace ->
stackTrace.toString()
}
}.joinToString(" ")
val cutLength = Math.min(exceptionAsStrings.length, 1000)
return exceptionAsStrings.substring(0, cutLength)
}

private fun getRequestParam(request: ServerHttpRequest): String {
return request.queryParams.map { param ->
@Suppress("IMPLICIT_CAST_TO_ANY")
val value = when (param.value.size == 1) {
true -> param.value.firstOrNull()
false -> param.value
}
"${param.key} : $value"
}.joinToString("\n")
}
}

private data class ErrorMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.oksusu.susu.api.event.listener
import com.oksusu.susu.api.common.aspect.SusuEventListener
import com.oksusu.susu.api.event.model.CacheUserEnvelopeStatisticEvent
import com.oksusu.susu.api.statistic.application.UserEnvelopeStatisticService
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.extension.mdcCoroutineScope
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.Dispatchers
Expand All @@ -13,12 +14,13 @@ import org.springframework.context.event.EventListener
@SusuEventListener
class StatisticEventListener(
private val userEnvelopeStatisticService: UserEnvelopeStatisticService,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
private val logger = KotlinLogging.logger { }

@EventListener
fun cacheUserEnvelopStatistic(event: CacheUserEnvelopeStatisticEvent) {
mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch {
mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch {
logger.info { "[${event.publishAt}] ${event.uid} 유저 봉투 통계 캐싱 시작" }

userEnvelopeStatisticService.save(event.uid, event.statistic)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.oksusu.susu.api.event.listener
import com.oksusu.susu.api.common.aspect.SusuEventListener
import com.oksusu.susu.api.event.model.SystemActionLogEvent
import com.oksusu.susu.api.log.application.SystemActionLogService
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.extension.mdcCoroutineScope
import com.oksusu.susu.domain.log.domain.SystemActionLog
import kotlinx.coroutines.Dispatchers
Expand All @@ -13,10 +14,11 @@ import org.springframework.context.event.EventListener
@SusuEventListener
class SystemActionLogEventListener(
private val systemActionLogService: SystemActionLogService,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
@EventListener
fun subscribe(event: SystemActionLogEvent) {
mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch {
mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch {
SystemActionLog(
ipAddress = event.ipAddress,
path = event.path,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.oksusu.susu.api.event.listener
import com.oksusu.susu.api.common.aspect.SusuEventListener
import com.oksusu.susu.api.event.model.TermAgreementHistoryCreateEvent
import com.oksusu.susu.api.term.application.TermAgreementHistoryService
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.extension.mdcCoroutineScope
import com.oksusu.susu.domain.term.domain.TermAgreementHistory
import io.github.oshai.kotlinlogging.KotlinLogging
Expand All @@ -14,12 +15,13 @@ import org.springframework.transaction.event.TransactionalEventListener
@SusuEventListener
class TermAgreementHistoryEventListener(
private val termAgreementHistoryService: TermAgreementHistoryService,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
private val logger = KotlinLogging.logger { }

@TransactionalEventListener
fun createTermAgreementHistoryService(event: TermAgreementHistoryCreateEvent) {
mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch {
mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch {
val uid = event.termAgreements.first().uid
val termIds = event.termAgreements.map { it.termId }
logger.info { "${event.publishAt}에 발행된 $uid 유저의 $termIds 번 term agreement history ${event.changeType} 실행 시작" }
Expand Down
Loading

0 comments on commit dbf1f42

Please sign in to comment.