Backend/Spring Framework

[Spring Boot] Slack 메시지 보내기

mopil 2023. 7. 16. 12:13
반응형

어플리케이션에서 슬랙으로 메시지를 보내는 방법에 대해 공유한다.

 

토큰을 사용한 봇 방법과 웹훅 방법이 있는데, 여기서는 웹훅 방법을 통해 구현한다.

 

# 웹훅 만들기

먼저 원하는 워크스페이스로 들어간 다음 웹훅을 생성한다.

incoming-webhook 앱을 검색해서 추가

add webhook을 누르면 이렇게 채널을 선택할 수 있다. 여기서 대표 채널 하나만 설정하고, 나중에 어플리케이션 레벨에서

하나의 웹 훅으로 여러 채널에서 공유해서 쓸거다.

 

이 URL을 잘 갖고있자.

 

# SlackClient

@Component
class SlackClient(
    private val restTemplate: RestTemplate,
    @Value("\${slack.webhook.url}") private val webhookUrl: String,
) {

    data class SlackMessage(
        val username: String,
        val text: String,
        @JsonProperty("icon_emoji")
        val iconEmoji: String,
        val channel: String
    )

    @Async
    fun sendSlack(
        senderName: String = "",
        message: String,
        iconEmoji: String = "",
        channel: String
    ) {
        val slackMessage = SlackMessage(
            username = senderName,
            text = message,
            iconEmoji = iconEmoji,
            channel = channel
        )
        val entity = HttpEntity<SlackMessage>(slackMessage)
        restTemplate.exchange(webhookUrl, HttpMethod.POST, entity, String::class.java)
    }
}

@Value()로 설정파일에 있는 웹훅을 가져온다. 혹시 모르니 시크릿 키처럼 관리하면 좋을 듯하다.

클라이언트 라이브러리는 스프링 내장 RestTemplate를 사용했다. 빈 등록을 해줘야 한다.

슬랙 메시지를 보내는 것은 비동기적으로 처리하도록 Async 어노테이션을 달아줬다.

이는 나중에 예외처리를 할 때 ControllerAdvice에서 처리하지 못하므로, 별도의 예외처리를 해줘야 한다.

 

각 프로퍼티에 대해 설명하자면,

  • senderName : 슬랙봇 이름을 설정한다.

  • text : 보낼 메시지
  • iconEmoji : 슬랙봇 프로필 이모지. 영문형식으로 :smile: 이렇게 작성하면 슬랙이 자동으로 인식한다. 추가로 슬랙으로는 icon_emoji 형식인 스네이크케이스로 보내야 인식하므로 주의

한글로 되어있으면 이모지 영문명을 보기 힘든데, 슬랙 언어를 잠시 영어로 바꿔주면 된다.

  • channel : 보낼 채널명을 입력. #alert-test이면 alert-test를 적으면 된다. 워크스페이스에 없는 채널명을 입력하면 404 에러가 뜨니 예외처리 해줘야 한다. 채널명은 원래 처음 웹훅을 등록한 채널로 되는데, 이게 오버라이드가 되어서 하나의 웹훅으로 여러 채널로 보낼 수 있다.

 

# Async 예외 처리

@Async는 ControllerAdvice 전역 예외처리를 할 수 없으므로, 별도의 예외처리 로직을 구현한다.

@Configuration
@EnableAsync
class AsyncConfig : AsyncConfigurer {

    override fun getAsyncUncaughtExceptionHandler(): AsyncUncaughtExceptionHandler {
        return AsyncUncaughtExceptionHandler { throwable, method, params ->
            if (throwable is HttpClientErrorException || throwable is HttpServerErrorException) {
                logger.warn("method:${method.name} params:${params.toList()} handleAsyncSlackException: ${throwable.message}")
            }
        }
    }
}

 

RestTemplate이 요청 서버에서 4xx, 5xx를 받으면 HttpClientErrorException, HttpServerErrorException을 던진다.

이를 적절하게 예외처리 해주면 된다.

 

# 사용

의존성 주입받아서 사용하면 된다.

@RestController
class TestController(
    private val slackClient: SlackClient
) {

    @GetMapping(Uris.Test.TEST)
    fun test() {
        slackClient.sendSlack(
            message = "test", channel = "alert-test",
            iconEmoji = ":ghost:", senderName = "test")
    }
}

 

반응형