diff --git a/api/src/main/kotlin/com/oksusu/susu/api/category/application/CategoryService.kt b/api/src/main/kotlin/com/oksusu/susu/api/category/application/CategoryService.kt index 05cb287d..cb6fc98b 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/category/application/CategoryService.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/category/application/CategoryService.kt @@ -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 @@ -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 = emptyMap() @@ -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 { diff --git a/api/src/main/kotlin/com/oksusu/susu/api/dev/DevBatchResource.kt b/api/src/main/kotlin/com/oksusu/susu/api/dev/DevBatchResource.kt index 09cbe650..30fe27e3 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/dev/DevBatchResource.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/dev/DevBatchResource.kt @@ -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 @@ -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() } } @@ -42,7 +44,7 @@ class DevBatchResource( suspend fun getDailySummaries( adminUser: AdminUser, ) { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { susuStatisticsDailySummaryJob.runDailySummaryJob() } } @@ -52,7 +54,7 @@ class DevBatchResource( suspend fun refreshSusuEnvelopeStatistic( adminUser: AdminUser, ) { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { susuEnvelopeStatisticJob.refreshSusuEnvelopeStatistic() } } @@ -63,7 +65,7 @@ class DevBatchResource( suspend fun deleteWithdrawUserData( adminUser: AdminUser, ) { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { deleteWithdrawUserDataJob.deleteWithdrawUserData() } } @@ -73,7 +75,7 @@ class DevBatchResource( suspend fun deleteWithdrawUserDataForWeek( adminUser: AdminUser, ) { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { deleteWithdrawUserDataJob.deleteWithdrawUserDataForWeek() } } @@ -83,7 +85,7 @@ class DevBatchResource( suspend fun imposeSanctionsAboutReportForDay( adminUser: AdminUser, ) { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { imposeSanctionsAboutReportJob.imposeSanctionsAboutReportForDay() } } @@ -94,7 +96,7 @@ class DevBatchResource( suspend fun updateReportCount( adminUser: AdminUser, ) { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { imposeSanctionsAboutReportJob.updateReportCount() } } @@ -105,7 +107,7 @@ class DevBatchResource( suspend fun updateUserCommunityPunishCount( adminUser: AdminUser, ) { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { imposeSanctionsAboutReportJob.updateUserCommunityPunishCount() } } @@ -116,7 +118,7 @@ class DevBatchResource( suspend fun refreshSusuEnvelopeStatisticAmount( adminUser: AdminUser, ) { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { susuEnvelopeStatisticJob.refreshSusuEnvelopeStatisticAmount() } } diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/AuthEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/AuthEventListener.kt index 0c2b6cd2..b1c28c62 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/AuthEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/AuthEventListener.kt @@ -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 @@ -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) { diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/CountEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/CountEventListener.kt index 80fac589..e164e077 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/CountEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/CountEventListener.kt @@ -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 @@ -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 { diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/EnvelopeEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/EnvelopeEventListener.kt index 0632ab7c..d47e2fc8 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/EnvelopeEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/EnvelopeEventListener.kt @@ -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 @@ -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 diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/LedgerEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/LedgerEventListener.kt index 7bc1b5ce..196b89de 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/LedgerEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/LedgerEventListener.kt @@ -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 @@ -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 } diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SentryCaptureExceptionEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SentryCaptureExceptionEventListener.kt index 7fb59a85..99c1e2e8 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SentryCaptureExceptionEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SentryCaptureExceptionEventListener.kt @@ -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 @@ -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 { } @@ -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 diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SlackErrorAlarmEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SlackErrorAlarmEventListener.kt index 05929d15..69893fcb 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SlackErrorAlarmEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SlackErrorAlarmEventListener.kt @@ -3,11 +3,11 @@ 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 @@ -15,13 +15,13 @@ 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) { @@ -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()) @@ -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( diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/StatisticEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/StatisticEventListener.kt index caadbbb4..3c8fa93c 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/StatisticEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/StatisticEventListener.kt @@ -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 @@ -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) diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SystemActionLogEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SystemActionLogEventListener.kt index c257cf48..e0db9b85 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SystemActionLogEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/SystemActionLogEventListener.kt @@ -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 @@ -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, diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/TermAgreementHistoryEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/TermAgreementHistoryEventListener.kt index 31632747..4c9cbfd3 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/TermAgreementHistoryEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/TermAgreementHistoryEventListener.kt @@ -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 @@ -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} 실행 시작" } diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserDeviceEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserDeviceEventListener.kt index df22fc07..01fd8551 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserDeviceEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserDeviceEventListener.kt @@ -4,6 +4,7 @@ import com.oksusu.susu.api.common.aspect.SusuEventListener import com.oksusu.susu.api.event.model.CreateUserDeviceEvent import com.oksusu.susu.api.event.model.UpdateUserDeviceEvent import com.oksusu.susu.api.user.application.UserDeviceService +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 @@ -18,12 +19,13 @@ import org.springframework.transaction.event.TransactionalEventListener class UserDeviceEventListener( private val userDeviceService: UserDeviceService, private val txTemplates: TransactionTemplates, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { private val logger = KotlinLogging.logger { } @TransactionalEventListener fun createUserDevice(event: CreateUserDeviceEvent) { - mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch { + mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch { logger.info { "${event.publishAt}에 발행된 ${event.userDevice.uid} 유저 디바이스 정보 저장 실행 시작" } txTemplates.writer.coExecuteOrNull { @@ -36,7 +38,7 @@ class UserDeviceEventListener( @TransactionalEventListener fun updateUserDevice(event: UpdateUserDeviceEvent) { - CoroutineScope(Dispatchers.IO + Job()).launch { + CoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler).launch { logger.info { "${event.publishAt}에 발행된 ${event.userDevice.uid} 유저 디바이스 정보 업데이트 실행 시작" } val userDevice = userDeviceService.findByUid(event.userDevice.uid) diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserStatusHistoryEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserStatusHistoryEventListener.kt index 10d3c1a0..7fc558d8 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserStatusHistoryEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserStatusHistoryEventListener.kt @@ -2,6 +2,7 @@ package com.oksusu.susu.api.event.listener import com.oksusu.susu.api.common.aspect.SusuEventListener import com.oksusu.susu.api.event.model.CreateUserStatusHistoryEvent +import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler import com.oksusu.susu.common.extension.mdcCoroutineScope import com.oksusu.susu.domain.user.infrastructure.UserStatusHistoryRepository import io.github.oshai.kotlinlogging.KotlinLogging @@ -13,12 +14,13 @@ import org.springframework.transaction.event.TransactionalEventListener @SusuEventListener class UserStatusHistoryEventListener( private val userStatusHistoryRepository: UserStatusHistoryRepository, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { private val logger = KotlinLogging.logger { } @TransactionalEventListener fun createUserStatusHistoryService(event: CreateUserStatusHistoryEvent) { - mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch { + mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch { val history = event.userStatusHistory logger.info { "[${event.publishAt}] ${history.uid} 유저 user status ${history.fromStatusId} -> ${history.toStatusId} 변경 시작" } diff --git a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserWithdrawEventListener.kt b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserWithdrawEventListener.kt index 807b4ca4..d5eb1cf1 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserWithdrawEventListener.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/event/listener/UserWithdrawEventListener.kt @@ -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.CreateUserWithdrawEvent import com.oksusu.susu.api.user.application.UserWithdrawService +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 @@ -13,12 +14,13 @@ import org.springframework.transaction.event.TransactionalEventListener @SusuEventListener class UserWithdrawEventListener( private val userWithdrawService: UserWithdrawService, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { private val logger = KotlinLogging.logger { } @TransactionalEventListener fun createUserWithdrawService(event: CreateUserWithdrawEvent) { - mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch { + mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch { val userWithdraw = event.userWithdraw logger.info { "[${event.publishAt}] ${userWithdraw.uid} 유저 탈퇴 엔티티 저장 시작" } diff --git a/api/src/main/kotlin/com/oksusu/susu/api/extension/ServerHttpExtension.kt b/api/src/main/kotlin/com/oksusu/susu/api/extension/ServerHttpExtension.kt index 82006115..4ab64c7a 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/extension/ServerHttpExtension.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/extension/ServerHttpExtension.kt @@ -6,3 +6,7 @@ val ServerHttpRequest.remoteIp get() = headers.getFirst("X-Forwarded-For")?.split(",")?.firstOrNull()?.trim() ?: this.remoteAddress?.address?.hostAddress ?: "" + +val ServerHttpRequest.requestParam + get() = this.queryParams.map { param -> "${param.key} : ${param.value}" } + .joinToString("\n") diff --git a/api/src/main/kotlin/com/oksusu/susu/api/friend/application/RelationshipService.kt b/api/src/main/kotlin/com/oksusu/susu/api/friend/application/RelationshipService.kt index 0f7afa6c..4b18dda5 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/friend/application/RelationshipService.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/friend/application/RelationshipService.kt @@ -1,6 +1,7 @@ package com.oksusu.susu.api.friend.application import com.oksusu.susu.api.friend.model.RelationshipModel +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 @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service @Service class RelationshipService( private val relationshipRepository: RelationshipRepository, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { private val logger = KotlinLogging.logger { } private var relationships: Map = emptyMap() @@ -26,7 +28,7 @@ class RelationshipService( initialDelayString = "\${oksusu.scheduled-tasks.refresh-relationships.initial-delay:0}" ) fun refreshRelationships() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { logger.info { "start refresh relationships" } relationships = runCatching { diff --git a/api/src/main/kotlin/com/oksusu/susu/api/post/application/BoardService.kt b/api/src/main/kotlin/com/oksusu/susu/api/post/application/BoardService.kt index e32fec3a..0ad07c61 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/post/application/BoardService.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/post/application/BoardService.kt @@ -1,6 +1,7 @@ package com.oksusu.susu.api.post.application import com.oksusu.susu.api.post.model.BoardModel +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 @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service @Service class BoardService( private val boardRepository: BoardRepository, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { private val logger = KotlinLogging.logger { } private var boards: Map = emptyMap() @@ -26,7 +28,7 @@ class BoardService( initialDelayString = "\${oksusu.scheduled-tasks.refresh-boards.initial-delay:0}" ) fun refreshBoards() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { logger.info { "start refresh boards" } boards = runCatching { diff --git a/api/src/main/kotlin/com/oksusu/susu/api/report/application/ReportMetadataService.kt b/api/src/main/kotlin/com/oksusu/susu/api/report/application/ReportMetadataService.kt index 8bcc5052..0222f0b5 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/report/application/ReportMetadataService.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/report/application/ReportMetadataService.kt @@ -1,6 +1,7 @@ package com.oksusu.susu.api.report.application import com.oksusu.susu.api.report.model.ReportMetadataModel +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 @@ -19,6 +20,7 @@ import org.springframework.stereotype.Service @Service class ReportMetadataService( private val reportMetadataRepository: ReportMetadataRepository, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { private val logger = KotlinLogging.logger { } private var reportMetadata: Map = emptyMap() @@ -28,7 +30,7 @@ class ReportMetadataService( initialDelayString = "\${oksusu.scheduled-tasks.refresh-report-metadata.initial-delay:0}" ) fun refreshRelationships() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { logger.info { "start refresh report metadata" } reportMetadata = runCatching { diff --git a/api/src/main/kotlin/com/oksusu/susu/api/user/application/UserStatusTypeService.kt b/api/src/main/kotlin/com/oksusu/susu/api/user/application/UserStatusTypeService.kt index 4a88c111..1c7b7849 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/user/application/UserStatusTypeService.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/user/application/UserStatusTypeService.kt @@ -1,6 +1,7 @@ package com.oksusu.susu.api.user.application import com.oksusu.susu.api.user.model.UserStatusTypeModel +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 @@ -16,6 +17,7 @@ import org.springframework.stereotype.Service @Service class UserStatusTypeService( private val userStatusTypeRepository: UserStatusTypeRepository, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { private val logger = KotlinLogging.logger { } private var statuses: Map = emptyMap() @@ -25,7 +27,7 @@ class UserStatusTypeService( initialDelayString = "\${oksusu.scheduled-tasks.refresh-statuses.initial-delay:0}" ) fun refreshStatuses() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { logger.info { "start refresh statuses" } statuses = runCatching { diff --git a/api/src/test/kotlin/com/oksusu/susu/api/post/application/BoardServiceTest.kt b/api/src/test/kotlin/com/oksusu/susu/api/post/application/BoardServiceTest.kt index 6d2051d6..04f86ab3 100644 --- a/api/src/test/kotlin/com/oksusu/susu/api/post/application/BoardServiceTest.kt +++ b/api/src/test/kotlin/com/oksusu/susu/api/post/application/BoardServiceTest.kt @@ -1,5 +1,6 @@ package com.oksusu.susu.api.post.application +import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler import com.oksusu.susu.common.exception.NotFoundException import com.oksusu.susu.domain.post.infrastructure.repository.BoardRepository import fixture.DomainFixtureUtil @@ -9,16 +10,18 @@ import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.equals.shouldBeEqual import io.mockk.every import io.mockk.mockk -import kotlinx.coroutines.test.* +import kotlinx.coroutines.CoroutineExceptionHandler class BoardServiceTest : DescribeSpec({ val logger = KotlinLogging.logger { } val mockBoardRepository = mockk() + val mockCoroutineExceptionHandler = mockk() - val boardService = BoardService(mockBoardRepository) + val boardService = BoardService(mockBoardRepository, mockCoroutineExceptionHandler) every { mockBoardRepository.findAllByIsActive(any()) } returns DomainFixtureUtil.getBoards(5) + every { mockCoroutineExceptionHandler.handler } returns CoroutineExceptionHandler { _, _ -> } beforeSpec { boardService.refreshBoards() diff --git a/batch/src/main/kotlin/com/oksusu/susu/batch/envelope/job/RefreshSusuEnvelopeStatisticJob.kt b/batch/src/main/kotlin/com/oksusu/susu/batch/envelope/job/RefreshSusuEnvelopeStatisticJob.kt index d8f53914..12d4b30a 100644 --- a/batch/src/main/kotlin/com/oksusu/susu/batch/envelope/job/RefreshSusuEnvelopeStatisticJob.kt +++ b/batch/src/main/kotlin/com/oksusu/susu/batch/envelope/job/RefreshSusuEnvelopeStatisticJob.kt @@ -330,9 +330,7 @@ class RefreshSusuEnvelopeStatisticJob( } /** amount 값 캐싱 */ - CoroutineScope(Dispatchers.IO).launch { - cacheService.set(Cache.getSusuEnvelopeStatisticAmountCache(), cache) - } + cacheService.set(Cache.getSusuEnvelopeStatisticAmountCache(), cache) logger.info { "finish refresh susu statistic" } } diff --git a/batch/src/main/kotlin/com/oksusu/susu/batch/envelope/scheduler/SusuEnvelopeStatisticScheduler.kt b/batch/src/main/kotlin/com/oksusu/susu/batch/envelope/scheduler/SusuEnvelopeStatisticScheduler.kt index ef81ff23..84e5a90c 100644 --- a/batch/src/main/kotlin/com/oksusu/susu/batch/envelope/scheduler/SusuEnvelopeStatisticScheduler.kt +++ b/batch/src/main/kotlin/com/oksusu/susu/batch/envelope/scheduler/SusuEnvelopeStatisticScheduler.kt @@ -1,6 +1,7 @@ package com.oksusu.susu.batch.envelope.scheduler import com.oksusu.susu.batch.envelope.job.RefreshSusuEnvelopeStatisticJob +import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -10,13 +11,14 @@ import org.springframework.stereotype.Component @Component class SusuEnvelopeStatisticScheduler( private val refreshSusuEnvelopeStatisticJob: RefreshSusuEnvelopeStatisticJob, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { @Scheduled( fixedRate = 1000 * 60 * 60, initialDelayString = "\${oksusu.scheduled-tasks.refresh-susu-envelope-statistic.initial-delay:100}" ) fun refreshSusuEnvelopeStatistic() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { refreshSusuEnvelopeStatisticJob.refreshSusuEnvelopeStatistic() } } diff --git a/batch/src/main/kotlin/com/oksusu/susu/batch/log/scheduler/SystemActionLogScheduler.kt b/batch/src/main/kotlin/com/oksusu/susu/batch/log/scheduler/SystemActionLogScheduler.kt index f898a016..b110568e 100644 --- a/batch/src/main/kotlin/com/oksusu/susu/batch/log/scheduler/SystemActionLogScheduler.kt +++ b/batch/src/main/kotlin/com/oksusu/susu/batch/log/scheduler/SystemActionLogScheduler.kt @@ -1,6 +1,7 @@ package com.oksusu.susu.batch.log.scheduler import com.oksusu.susu.batch.log.job.SystemActionLogDeleteJob +import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -10,6 +11,7 @@ import org.springframework.stereotype.Component @Component class SystemActionLogScheduler( private val systemActionLogDeleteJob: SystemActionLogDeleteJob, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { /** 한달이 지난 system action log 삭제 처리 */ @Scheduled( @@ -17,7 +19,7 @@ class SystemActionLogScheduler( initialDelayString = "\${oksusu.scheduled-tasks.delete-system-action-log.initial-delay:100}" ) fun runDeleteJob() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { systemActionLogDeleteJob.runDeleteJob() } } diff --git a/batch/src/main/kotlin/com/oksusu/susu/batch/report/scheduler/ReportScheduler.kt b/batch/src/main/kotlin/com/oksusu/susu/batch/report/scheduler/ReportScheduler.kt index 132a50b4..a3f6fa3e 100644 --- a/batch/src/main/kotlin/com/oksusu/susu/batch/report/scheduler/ReportScheduler.kt +++ b/batch/src/main/kotlin/com/oksusu/susu/batch/report/scheduler/ReportScheduler.kt @@ -1,6 +1,7 @@ package com.oksusu.susu.batch.report.scheduler import com.oksusu.susu.batch.report.job.ImposeSanctionsAboutReportJob +import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -10,10 +11,11 @@ import org.springframework.stereotype.Component @Component class ReportScheduler( private val imposeSanctionsAboutReportJob: ImposeSanctionsAboutReportJob, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { @Scheduled(cron = "0 0 0 * * *") fun imposeSanctionsAboutReportForDay() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { imposeSanctionsAboutReportJob.imposeSanctionsAboutReportForDay() } } diff --git a/batch/src/main/kotlin/com/oksusu/susu/batch/summary/scheduler/SusuStatisticsSummaryScheduler.kt b/batch/src/main/kotlin/com/oksusu/susu/batch/summary/scheduler/SusuStatisticsSummaryScheduler.kt index d3c914da..8d95cb64 100644 --- a/batch/src/main/kotlin/com/oksusu/susu/batch/summary/scheduler/SusuStatisticsSummaryScheduler.kt +++ b/batch/src/main/kotlin/com/oksusu/susu/batch/summary/scheduler/SusuStatisticsSummaryScheduler.kt @@ -2,6 +2,7 @@ package com.oksusu.susu.batch.summary.scheduler import com.oksusu.susu.batch.summary.job.SusuStatisticsDailySummaryJob import com.oksusu.susu.batch.summary.job.SusuStatisticsHourSummaryJob +import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler import com.oksusu.susu.common.config.environment.EnvironmentType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -15,17 +16,18 @@ import org.springframework.stereotype.Component class SusuStatisticsSummaryScheduler( private val hourSummaryJob: SusuStatisticsHourSummaryJob, private val dailySummaryJob: SusuStatisticsDailySummaryJob, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { @Scheduled(cron = "0 0 0/1 * * *") fun runHourSummary() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { hourSummaryJob.runHourSummaryJob() } } @Scheduled(cron = "0 0 9 * * *") fun runDailySummary() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { dailySummaryJob.runDailySummaryJob() } } diff --git a/batch/src/main/kotlin/com/oksusu/susu/batch/user/scheduler/UserScheduler.kt b/batch/src/main/kotlin/com/oksusu/susu/batch/user/scheduler/UserScheduler.kt index 49730b7d..7519ee47 100644 --- a/batch/src/main/kotlin/com/oksusu/susu/batch/user/scheduler/UserScheduler.kt +++ b/batch/src/main/kotlin/com/oksusu/susu/batch/user/scheduler/UserScheduler.kt @@ -1,6 +1,7 @@ package com.oksusu.susu.batch.user.scheduler import com.oksusu.susu.batch.user.job.DeleteWithdrawUserDataJob +import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -9,11 +10,12 @@ import org.springframework.stereotype.Component @Component class UserScheduler( - val deleteWithdrawUserDataJob: DeleteWithdrawUserDataJob, + private val deleteWithdrawUserDataJob: DeleteWithdrawUserDataJob, + private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, ) { @Scheduled(cron = "0 0 3 * * *") fun deleteWithdrawUserData() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { deleteWithdrawUserDataJob.deleteWithdrawUserDataForWeek() } } diff --git a/client/src/main/kotlin/com/oksusu/susu/client/common/coroutine/ErrorPublishingCoroutineExceptionHandler.kt b/client/src/main/kotlin/com/oksusu/susu/client/common/coroutine/ErrorPublishingCoroutineExceptionHandler.kt new file mode 100644 index 00000000..f923a49b --- /dev/null +++ b/client/src/main/kotlin/com/oksusu/susu/client/common/coroutine/ErrorPublishingCoroutineExceptionHandler.kt @@ -0,0 +1,49 @@ +package com.oksusu.susu.client.common.coroutine + +import com.oksusu.susu.client.slack.SlackClient +import com.oksusu.susu.client.slack.model.SlackMessageModel +import com.oksusu.susu.common.extension.LoggingCoroutineExceptionHandler +import com.oksusu.susu.common.extension.errorStack +import com.oksusu.susu.common.extension.format +import com.oksusu.susu.common.extension.isProd +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.springframework.core.env.Environment +import org.springframework.stereotype.Component +import java.time.LocalDateTime + +@Component +class ErrorPublishingCoroutineExceptionHandler( + private val slackClient: SlackClient, + private val environment: Environment, +) { + val handler = CoroutineExceptionHandler { _, throwable -> + handle(throwable) + } + + private val logger = KotlinLogging.logger { } + + private fun handle(exception: Throwable) { + val errorMessage = exception.toString() + val errorStack = exception.errorStack + + if (environment.isProd()) { + CoroutineScope(Dispatchers.IO + LoggingCoroutineExceptionHandler).launch { + slackClient.sendError( + SlackMessageModel( + """ + * 스케줄러 에러 발생 ${LocalDateTime.now().format("yyyy-MM-dd HH:mm:ss")}* + - Message : $errorMessage + - Stack Trace : $errorStack + """.trimIndent() + ) + ) + } + } + + logger.error { errorStack } + } +} diff --git a/common/src/main/kotlin/com/oksusu/susu/common/extension/CoroutineExtension.kt b/common/src/main/kotlin/com/oksusu/susu/common/extension/CoroutineExtension.kt index 79f11821..ceb8ed96 100644 --- a/common/src/main/kotlin/com/oksusu/susu/common/extension/CoroutineExtension.kt +++ b/common/src/main/kotlin/com/oksusu/susu/common/extension/CoroutineExtension.kt @@ -2,14 +2,18 @@ package com.oksusu.susu.common.extension import arrow.fx.coroutines.parZip import com.oksusu.susu.common.consts.MDC_KEY_TRACE_ID -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.* import kotlinx.coroutines.slf4j.MDCContext -import kotlinx.coroutines.withContext import org.slf4j.MDC import kotlin.coroutines.CoroutineContext +private val logger = KotlinLogging.logger { } + +val LoggingCoroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> + logger.error { "$exception ${exception.errorStack}" } +} + suspend fun withMDCContext( context: CoroutineContext = Dispatchers.IO, block: suspend () -> T, diff --git a/common/src/main/kotlin/com/oksusu/susu/common/extension/ExceptionExtension.kt b/common/src/main/kotlin/com/oksusu/susu/common/extension/ExceptionExtension.kt new file mode 100644 index 00000000..4ac83912 --- /dev/null +++ b/common/src/main/kotlin/com/oksusu/susu/common/extension/ExceptionExtension.kt @@ -0,0 +1,21 @@ +package com.oksusu.susu.common.extension + +val Throwable.supressedErrorStack: String + get() = run { + val exceptionAsStrings = this.suppressedExceptions.flatMap { exception -> + exception.stackTrace.map { stackTrace -> + stackTrace.toString() + } + }.joinToString(" ") + val cutLength = exceptionAsStrings.length.coerceAtMost(1000) + return exceptionAsStrings.substring(0, cutLength) + } + +val Throwable.errorStack: String + get() = run { + val exceptionAsStrings = this.stackTrace.joinToString("\n") { stackTrace -> + stackTrace.toString() + } + val cutLength = exceptionAsStrings.length.coerceAtMost(1000) + return exceptionAsStrings.substring(0, cutLength) + }