Skip to content

Commit e42464a

Browse files
BlackBean99kdomo
authored andcommitted
[DPMBE-66] 슬랙 에러알림 기능을 추가한다 (#91)
* feat : slack sending * feat : 비동기 메시징 * refactor : Value 주입 값 변경
1 parent f256e94 commit e42464a

File tree

8 files changed

+217
-0
lines changed

8 files changed

+217
-0
lines changed

Whatnow-Infrastructure/build.gradle.kts

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ dependencies{
99
api ("org.redisson:redisson:3.19.0")
1010
api ("com.amazonaws:aws-java-sdk-s3:1.12.476")
1111
api ("com.google.firebase:firebase-admin:9.1.1")
12+
// slack
13+
api ("com.slack.api:slack-api-client:1.27.2")
14+
1215

1316
implementation(project(":Whatnow-Common"))
1417

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.depromeet.whatnow.api.config.slack
2+
3+
import com.depromeet.whatnow.config.slack.SlackErrorNotificationProvider
4+
import com.slack.api.model.block.Blocks
5+
import com.slack.api.model.block.Blocks.divider
6+
import com.slack.api.model.block.Blocks.section
7+
import com.slack.api.model.block.LayoutBlock
8+
import com.slack.api.model.block.composition.BlockCompositions.plainText
9+
import com.slack.api.model.block.composition.MarkdownTextObject
10+
import org.springframework.stereotype.Component
11+
12+
@Component
13+
class SlackAsyncErrorSender(
14+
val slackProvider: SlackErrorNotificationProvider,
15+
) {
16+
fun execute(methodName: String, throwable: Throwable, params: Array<Any>) {
17+
val layoutBlocks = createLayoutBlocks(methodName, throwable, params)
18+
slackProvider.sendNotification(layoutBlocks)
19+
}
20+
21+
private fun createLayoutBlocks(methodName: String, throwable: Throwable, params: Array<Any>): List<LayoutBlock> {
22+
val layoutBlocks = mutableListOf<LayoutBlock>()
23+
24+
layoutBlocks.add(
25+
Blocks.header { headerBlockBuilder ->
26+
headerBlockBuilder.text(plainText("비동기 에러 알림"))
27+
},
28+
)
29+
layoutBlocks.add(divider())
30+
31+
val errorUserIdMarkdown = MarkdownTextObject.builder()
32+
.text("* 메소드 이름 :* $methodName")
33+
.build()
34+
35+
val errorUserIpMarkdown = MarkdownTextObject.builder()
36+
.text("* 요청 파라미터 :* ${getParamsToString(params)}".trimIndent())
37+
.build()
38+
39+
layoutBlocks.add(
40+
section { section ->
41+
section.fields(listOf(errorUserIdMarkdown, errorUserIpMarkdown))
42+
},
43+
)
44+
layoutBlocks.add(divider())
45+
46+
val errorStack = slackProvider.getErrorStack(throwable)
47+
val message = throwable.toString()
48+
49+
val errorNameMarkdown = MarkdownTextObject.builder()
50+
.text("* Message :* $message")
51+
.build()
52+
val errorStackMarkdown = MarkdownTextObject.builder()
53+
.text("* Stack Trace : :* $errorStack")
54+
.build()
55+
56+
layoutBlocks.add(
57+
section { section ->
58+
section.fields(listOf(errorNameMarkdown, errorStackMarkdown))
59+
},
60+
)
61+
return layoutBlocks
62+
}
63+
64+
private fun getParamsToString(params: Array<Any>): String {
65+
return params.joinToString(separator = ", ")
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.depromeet.whatnow.config.slack
2+
3+
import com.slack.api.model.block.LayoutBlock
4+
import org.springframework.beans.factory.annotation.Value
5+
import org.springframework.scheduling.annotation.Async
6+
import org.springframework.stereotype.Component
7+
import java.util.Arrays
8+
9+
@Component
10+
class SlackErrorNotificationProvider(
11+
val slackHelper: SlackHelper,
12+
@Value("\${slack.channel.id}")
13+
private val CHANNEL_ID: String,
14+
) {
15+
val MAX_LENGTH = 500
16+
17+
fun getErrorStack(throwable: Throwable): String {
18+
val exceptionAsStrings = Arrays.toString(throwable.stackTrace)
19+
val cutLength = Math.min(exceptionAsStrings.length, MAX_LENGTH)
20+
return exceptionAsStrings.substring(0, cutLength)
21+
}
22+
23+
@Async
24+
fun sendNotification(layoutBlocks: List<LayoutBlock>) {
25+
slackHelper.sendNotification(CHANNEL_ID!!, layoutBlocks)
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.depromeet.whatnow.config.slack
2+
3+
import com.depromeet.whatnow.helper.SpringEnvironmentHelper
4+
import com.slack.api.methods.MethodsClient
5+
import com.slack.api.methods.SlackApiException
6+
import com.slack.api.methods.request.chat.ChatPostMessageRequest
7+
import com.slack.api.model.block.LayoutBlock
8+
import org.slf4j.Logger
9+
import org.slf4j.LoggerFactory
10+
import org.springframework.stereotype.Component
11+
12+
@Component
13+
class SlackHelper(
14+
val springEnvironmentHelper: SpringEnvironmentHelper,
15+
val methodsClient: MethodsClient,
16+
) {
17+
val logger: Logger = LoggerFactory.getLogger(SlackHelper::class.java)
18+
fun sendNotification(CHANNEL_ID: String, layoutBlocks: List<LayoutBlock>) {
19+
if (!springEnvironmentHelper.isProdAndDevProfile) {
20+
return
21+
}
22+
val chatPostMessageRequest = ChatPostMessageRequest.builder()
23+
.channel(CHANNEL_ID)
24+
.text("")
25+
.blocks(layoutBlocks)
26+
.build()
27+
try {
28+
methodsClient.chatPostMessage(chatPostMessageRequest)
29+
} catch (slackApiException: SlackApiException) {
30+
logger.error(slackApiException.toString())
31+
}
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.depromeet.whatnow.config.slack
2+
3+
import com.slack.api.Slack
4+
import com.slack.api.webhook.Payload
5+
import org.springframework.beans.factory.annotation.Value
6+
import org.springframework.stereotype.Service
7+
import java.lang.RuntimeException
8+
import java.net.UnknownHostException
9+
10+
@Service
11+
class SlackMessageProvider(
12+
@Value("\${slack.webhook.username}")
13+
val username: String,
14+
15+
@Value("\${slack.webhook.icon-url}")
16+
val iconUrl: String,
17+
) {
18+
19+
private fun send(url: String, text: String) {
20+
val slack = Slack.getInstance()
21+
val payload = Payload.builder()
22+
.text(text)
23+
.username(username)
24+
.iconUrl(iconUrl)
25+
.build()
26+
try {
27+
val responseBody = slack.send(url, payload).body
28+
if (!responseBody.equals("ok")) {
29+
throw UnknownHostException("Slack Id 가 올바르지 않습니다.")
30+
}
31+
} catch (error: UnknownHostException) {
32+
throw error
33+
} catch (error: Exception) {
34+
throw RuntimeException(error)
35+
}
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.depromeet.whatnow.config.slack
2+
3+
import com.slack.api.model.block.LayoutBlock
4+
import org.springframework.beans.factory.annotation.Value
5+
import org.springframework.stereotype.Component
6+
7+
@Component
8+
class SlackServiceNotificationProvider(
9+
val slackHelper: SlackHelper,
10+
11+
@Value("\${slack.channel.id}")
12+
private val CHANNEL_ID: String,
13+
) {
14+
15+
fun sendNotification(layoutBlocks: MutableList<LayoutBlock>) {
16+
slackHelper.sendNotification(CHANNEL_ID!!, layoutBlocks)
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.depromeet.whatnow.config.slack.config
2+
3+
import com.slack.api.Slack
4+
import com.slack.api.methods.MethodsClient
5+
import org.springframework.beans.factory.annotation.Value
6+
import org.springframework.context.annotation.Bean
7+
import org.springframework.context.annotation.Configuration
8+
9+
@Configuration
10+
class SlackApiConfig(
11+
@Value("\${slack.webhook.token}")
12+
private val token: String,
13+
) {
14+
15+
@get:Bean
16+
val client: MethodsClient
17+
get() {
18+
val slackClient = Slack.getInstance()
19+
return slackClient.methods(token)
20+
}
21+
}

Whatnow-Infrastructure/src/main/resources/application-infrastructure.yml

+11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ spring:
1313
port: ${REDIS_PORT:6379}
1414
password: ${REDIS_PASSWORD:}
1515

16+
slack:
17+
webhook:
18+
token: ${SLACK_WEBHOOK_TOKEN:}
19+
username: ${SLACK_USERNAME:}
20+
icon-url: ${SLACK_ICON_EMOJI:}
21+
22+
webhook-url: ${SLACK_WEBHOOK_URL:}
23+
channel:
24+
id: ${SLACK_CHANNEL:}
25+
26+
1627
oauth:
1728
kakao:
1829
base-url: ${KAKAO_BASE_URL}

0 commit comments

Comments
 (0)