-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: slack 전송 에러 처리 로직 추가 (MZ-302) (#77)
* feat: slack 전송 실패 메세지 처리 로직 추가 * chore: ktlint format
- Loading branch information
Showing
12 changed files
with
263 additions
and
14 deletions.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
batch/src/main/kotlin/com/oksusu/susu/batch/slack/job/ResendFailedSentSlackMessageJob.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package com.oksusu.susu.batch.slack.job | ||
|
||
import com.oksusu.susu.cache.key.Cache | ||
import com.oksusu.susu.cache.model.FailedSentSlackMessageCache | ||
import com.oksusu.susu.cache.service.CacheService | ||
import com.oksusu.susu.client.slack.SlackClient | ||
import com.oksusu.susu.client.slack.model.SlackMessageModel | ||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import kotlinx.coroutines.* | ||
import org.springframework.stereotype.Component | ||
import java.time.LocalDateTime | ||
|
||
@Component | ||
class ResendFailedSentSlackMessageJob( | ||
private val cacheService: CacheService, | ||
private val slackClient: SlackClient, | ||
) { | ||
private val logger = KotlinLogging.logger { } | ||
|
||
companion object { | ||
private const val RESEND_BEFORE_MINUTES = 1L | ||
} | ||
|
||
suspend fun resendFailedSentSlackMessage() { | ||
logger.info { "start resend failed sent slack message" } | ||
|
||
// 1분 전에 실패한 것이 타겟 (현재가 24분이면 23분을 말하는 것) | ||
val targetTime = LocalDateTime.now().minusMinutes(RESEND_BEFORE_MINUTES) | ||
|
||
// 실패 메세지 조회 및 삭제 | ||
val failedMessages = withContext(Dispatchers.IO) { | ||
cacheService.sGetMembers(Cache.getFailedSentSlackMessageCache(targetTime)) | ||
} | ||
|
||
withContext(Dispatchers.IO) { | ||
cacheService.sDelete(Cache.getFailedSentSlackMessageCache(targetTime)) | ||
} | ||
|
||
// 다수 메세지 token 별로 하나의 메세지로 병합 | ||
val message = mergeFailedMessage(failedMessages) | ||
|
||
// 재전송 | ||
runCatching { | ||
coroutineScope { | ||
val sendDeferreds = message.map { (token, message) -> | ||
val slackMessageModel = SlackMessageModel(text = message) | ||
|
||
async(Dispatchers.IO) { | ||
slackClient.sendMessage( | ||
message = slackMessageModel, | ||
token = token, | ||
withRecover = false | ||
) | ||
} | ||
}.toTypedArray() | ||
|
||
awaitAll(*sendDeferreds) | ||
} | ||
}.onFailure { | ||
// 재전송 실패시 1분 뒤에 다시 보낼 수 있게, 1분 뒤에 보내는 메세지 목록에 추가 | ||
logger.warn { "postpone resend slack message" } | ||
|
||
postponeResendTimeOfFailedMessage(targetTime, message) | ||
} | ||
|
||
logger.info { "finish resend failed sent slack message" } | ||
} | ||
|
||
private suspend fun mergeFailedMessage(failedMessages: List<FailedSentSlackMessageCache>): Map<String, String> { | ||
val message = mutableMapOf<String, String>() | ||
|
||
failedMessages.forEach { model -> | ||
val recoverMsg = if (model.isStacked) { | ||
model.message | ||
} else { | ||
"[RECOVER - ${model.failedAt} slack failure] ${model.message}" | ||
} | ||
|
||
val stackedMessage = message[model.token] | ||
|
||
message[model.token] = if (stackedMessage == null) { | ||
recoverMsg | ||
} else { | ||
"$stackedMessage\n$recoverMsg" | ||
} | ||
} | ||
|
||
return message | ||
} | ||
|
||
private suspend fun postponeResendTimeOfFailedMessage(targetTime: LocalDateTime, message: Map<String, String>) { | ||
val nextTime = targetTime.plusMinutes(RESEND_BEFORE_MINUTES) | ||
|
||
coroutineScope { | ||
message.map { (token, message) -> | ||
val model = FailedSentSlackMessageCache( | ||
token = token, | ||
message = message, | ||
isStacked = true | ||
) | ||
|
||
async(Dispatchers.IO) { | ||
cacheService.sSet( | ||
cache = Cache.getFailedSentSlackMessageCache(nextTime), | ||
value = model | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
...ain/kotlin/com/oksusu/susu/batch/slack/scheduler/ResendFailedSentSlackMessageScheduler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.oksusu.susu.batch.slack.scheduler | ||
|
||
import com.oksusu.susu.batch.slack.job.ResendFailedSentSlackMessageJob | ||
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler | ||
import com.oksusu.susu.common.extension.resolveCancellation | ||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.launch | ||
import org.springframework.scheduling.annotation.Scheduled | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
class ResendFailedSentSlackMessageScheduler( | ||
private val resendFailedSentSlackMessageJob: ResendFailedSentSlackMessageJob, | ||
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler, | ||
) { | ||
private val logger = KotlinLogging.logger { } | ||
|
||
@Scheduled( | ||
fixedRate = 1000 * 60, | ||
initialDelayString = "\${oksusu.scheduled-tasks.resend-failed-sent-slack-message.initial-delay:100}" | ||
) | ||
fun resendFailedSentSlackMessageJob() { | ||
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch { | ||
runCatching { | ||
resendFailedSentSlackMessageJob.resendFailedSentSlackMessage() | ||
}.onFailure { e -> | ||
logger.resolveCancellation("[BATCH] fail to run resendFailedSentSlackMessageJob", e) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
cache/src/main/kotlin/com/oksusu/susu/cache/model/FailedSentSlackMessageCache.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.oksusu.susu.cache.model | ||
|
||
import java.time.LocalDateTime | ||
|
||
data class FailedSentSlackMessageCache( | ||
val token: String, | ||
val message: String, | ||
val failedAt: LocalDateTime = LocalDateTime.now(), | ||
val isStacked: Boolean = false, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters