Skip to content

Commit ab475b9

Browse files
committed
feat: 에러 발생시 슬랙 채널 알림 자동
1 parent ffc3577 commit ab475b9

File tree

3 files changed

+32
-18
lines changed

3 files changed

+32
-18
lines changed

tuk-api/src/main/kotlin/nexters/tuk/infrastructure/slack/SlackAlertSender.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package nexters.tuk.infrastructure.slack
33
import com.slack.api.methods.MethodsClient
44
import mu.KotlinLogging
55
import org.springframework.beans.factory.annotation.Value
6+
import org.springframework.scheduling.annotation.Async
67
import org.springframework.stereotype.Component
78

89
private val logger = KotlinLogging.logger {}
@@ -13,7 +14,8 @@ class SlackAlertSender(
1314
@Value("\${slack.channels.error-alert}")
1415
private val alertChannel: String,
1516
) {
16-
17+
18+
@Async
1719
fun sendAlert(slackErrorAlert: SlackErrorAlert) {
1820
alertChannel.takeIf { it.isNotBlank() }
1921
?.let { channel ->

tuk-api/src/main/kotlin/nexters/tuk/infrastructure/slack/SlackErrorAlert.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ data class SlackErrorAlert(
1313
val httpMethod: String,
1414
val path: String,
1515
val occurredAt: ZonedDateTime,
16-
val errorMessage: String,
17-
val requestParamsJson: String
16+
val errorMessage: String
1817
) {
1918
private fun kstString(): String =
2019
occurredAt.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
@@ -50,11 +49,6 @@ data class SlackErrorAlert(
5049
val errorMd = "❌ *에러 메시지*\n```$errorMessage```"
5150
blocks += Blocks.section { it.text(MarkdownTextObject(errorMd, false)) }
5251

53-
// 요청 파라미터(코드블록, 비어있지 않을 때만)
54-
if (requestParamsJson.isNotBlank()) {
55-
val paramsMd = "📦 *요청 파라미터*\n```$requestParamsJson```"
56-
blocks += Blocks.section { it.text(MarkdownTextObject(paramsMd, false)) }
57-
}
5852

5953
return blocks
6054
}

tuk-api/src/main/kotlin/nexters/tuk/ui/ApiControllerAdvice.kt

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import com.fasterxml.jackson.databind.exc.MismatchedInputException
66
import nexters.tuk.contract.ApiResponse
77
import nexters.tuk.contract.BaseException
88
import nexters.tuk.contract.ErrorType
9+
import nexters.tuk.infrastructure.slack.SlackAlertSender
10+
import nexters.tuk.infrastructure.slack.SlackErrorAlert
911
import org.slf4j.LoggerFactory
1012
import org.springframework.http.ResponseEntity
1113
import org.springframework.http.converter.HttpMessageNotReadableException
@@ -14,33 +16,40 @@ import org.springframework.web.bind.annotation.RestControllerAdvice
1416
import org.springframework.web.server.MissingRequestValueException
1517
import org.springframework.web.server.ServerWebInputException
1618
import org.springframework.web.servlet.resource.NoResourceFoundException
19+
import jakarta.servlet.http.HttpServletRequest
20+
import java.time.ZonedDateTime
1721

1822
@RestControllerAdvice
19-
class ApiControllerAdvice {
23+
class ApiControllerAdvice(
24+
private val slackAlertSender: SlackAlertSender
25+
) {
2026
private val log = LoggerFactory.getLogger(ApiControllerAdvice::class.java)
2127

2228
@ExceptionHandler
23-
fun handle(e: BaseException): ResponseEntity<ApiResponse<*>> {
29+
fun handle(e: BaseException, request: HttpServletRequest): ResponseEntity<ApiResponse<*>> {
2430
log.warn("BaseException : {}", e.message, e)
31+
sendSlackAlert(request, e.errorType, e.message)
2532
return failureResponse(errorType = e.errorType, errorMessage = e.message)
2633
}
2734

2835
@ExceptionHandler
29-
fun handle(e: IllegalArgumentException): ResponseEntity<ApiResponse<*>> {
36+
fun handle(e: IllegalArgumentException, request: HttpServletRequest): ResponseEntity<ApiResponse<*>> {
3037
log.warn("BaseException : {}", e.message, e)
38+
sendSlackAlert(request, ErrorType.BAD_REQUEST, e.message)
3139
return failureResponse(errorType = ErrorType.BAD_REQUEST, errorMessage = e.message)
3240
}
3341

3442
@ExceptionHandler
35-
fun handle(e: MissingRequestValueException): ResponseEntity<ApiResponse<*>> {
43+
fun handle(e: MissingRequestValueException, request: HttpServletRequest): ResponseEntity<ApiResponse<*>> {
3644
val name = e.methodParameter?.parameter?.name
3745
val type = e.methodParameter?.parameter?.type?.simpleName
3846
val message = "필수 요청 파라미터 '$name' (타입: $type)가 누락되었습니다."
47+
sendSlackAlert(request, ErrorType.BAD_REQUEST, message)
3948
return failureResponse(errorType = ErrorType.BAD_REQUEST, errorMessage = message)
4049
}
4150

4251
@ExceptionHandler
43-
fun handle(e: HttpMessageNotReadableException): ResponseEntity<ApiResponse<*>> {
52+
fun handle(e: HttpMessageNotReadableException, request: HttpServletRequest): ResponseEntity<ApiResponse<*>> {
4453
val errorMessage = when (val rootCause = e.rootCause) {
4554
is InvalidFormatException -> {
4655
val fieldName = rootCause.path.joinToString(".") { it.fieldName ?: "?" }
@@ -74,11 +83,12 @@ class ApiControllerAdvice {
7483
else -> "요청 본문을 처리하는 중 오류가 발생했습니다. JSON 메세지 규격을 확인해주세요."
7584
}
7685

86+
sendSlackAlert(request, ErrorType.BAD_REQUEST, errorMessage)
7787
return failureResponse(errorType = ErrorType.BAD_REQUEST, errorMessage = errorMessage)
7888
}
7989

8090
@ExceptionHandler
81-
fun handleBadRequest(e: ServerWebInputException): ResponseEntity<ApiResponse<*>> {
91+
fun handleBadRequest(e: ServerWebInputException, request: HttpServletRequest): ResponseEntity<ApiResponse<*>> {
8292
val errorMessage = when (val rootCause = e.rootCause) {
8393
is InvalidFormatException -> {
8494
val fieldName = rootCause.path.joinToString(".") { it.fieldName ?: "?" }
@@ -114,19 +124,27 @@ class ApiControllerAdvice {
114124
else -> "요청 본문을 처리하는 중 오류가 발생했습니다. JSON 메세지 규격을 확인해주세요."
115125
}
116126

127+
sendSlackAlert(request, ErrorType.BAD_REQUEST, errorMessage)
117128
return failureResponse(errorType = ErrorType.BAD_REQUEST, errorMessage = errorMessage)
118129
}
119130

120131
@ExceptionHandler
121-
fun handleNotFound(e: NoResourceFoundException): ResponseEntity<ApiResponse<*>> {
132+
fun handleNotFound(e: NoResourceFoundException, request: HttpServletRequest): ResponseEntity<ApiResponse<*>> {
133+
val message = "리소스를 찾을 수 없습니다: ${request.requestURI}"
134+
sendSlackAlert(request, ErrorType.NOT_FOUND, message)
122135
return failureResponse(errorType = ErrorType.NOT_FOUND)
123136
}
124137

125138
@ExceptionHandler
126-
fun handle(e: Throwable): ResponseEntity<ApiResponse<*>> {
139+
fun handle(e: Throwable, request: HttpServletRequest): ResponseEntity<ApiResponse<*>> {
127140
log.error("Exception : {}", e.message, e)
128-
val errorType = ErrorType.INTERNAL_ERROR
129-
return failureResponse(errorType = errorType)
141+
val message = e.message ?: "알 수 없는 서버 오류가 발생했습니다"
142+
sendSlackAlert(request, ErrorType.INTERNAL_ERROR, message)
143+
return failureResponse(errorType = ErrorType.INTERNAL_ERROR)
144+
}
145+
146+
private fun sendSlackAlert(request: HttpServletRequest, errorType: ErrorType, errorMessage: String? = null) {
147+
slackAlertSender.sendAlert(SlackErrorAlert(errorType.status.value(), request.method, request.requestURI, ZonedDateTime.now(), errorMessage ?: errorType.message))
130148
}
131149

132150
private fun failureResponse(errorType: ErrorType, errorMessage: String? = null): ResponseEntity<ApiResponse<*>> =

0 commit comments

Comments
 (0)