Backend/Spring

Springboot Slack으로 알림 받기

seung_soos 2023. 9. 11. 22:04

시작하며

Error 또는 중요한 배치 시 알람을 받을 방법으로 회사에서 사용하는 Slack을 접목시켰다.

 

링크 : https://github.com/seungsoos/slack-demo

 

Slack 의존성 추가 및 yml 설정

	implementation("com.slack.api:bolt:1.18.0")
	implementation("com.slack.api:bolt-servlet:1.18.0")
	implementation("com.slack.api:bolt-jetty:1.18.0")

 

구글링을 통해 방법을 찾고, 해당 토큰을 입력한다.

 

코드내용은 아래와 같다.

@Slf4j
@Component
@RequiredArgsConstructor
public class SlackComponentImpl implements SlackComponent {

    /**
     * Slack의 경우 하나의 채널당 tps 1 사용
     */
    @Value("${slack.send.tps}")
    private Integer tps;

    @Value(value = "${slack.token}")
    private String slackToken;
    private MethodsClient methods = null;

    private int tpsSendCount = 0;

    /**
     * 순서 보장 및 데이터 유실 방지를 위한 Queue 사용
     */
    BlockingQueue<SlackInnerResponse> blockingQueue = new LinkedBlockingQueue();

    @PostConstruct
    private void init() {
        methods = Slack.getInstance().methods(slackToken);
    }

    @Async
    public void addQueue(String message, SlackChannel slackChannel){
        SlackInnerResponse slackInnerResponse = new SlackInnerResponse(message, slackChannel);
        blockingQueue.add(slackInnerResponse);

        startSendMessage();
    }

    private void startSendMessage() {
        SlackInnerResponse poll = blockingQueue.poll();
        if(Objects.isNull(poll)){
            return;
        }
        sendMessage(poll.message, poll.slackChannel);
    }

    @AllArgsConstructor
    static class SlackInnerResponse{
        private String message;
        private SlackChannel slackChannel;
    }


    /**
     * Slack 메시지 전송 메서드
     * 비동기 처리
     */

    private synchronized void sendMessage(String message, SlackChannel slackChannel) {
        log.info("sendSlackMessage message = {}, channel ={}", message, slackChannel.desc());

        for (int i = 0; i < 3; i++) {
            if (getTpsSendCount() >= tps) {
                log.info("thread Sleep");
                /**
                 * SlackApiException: status: 429 발생으로 TPS 조절 필요
                 * https://api.slack.com/docs/rate-limits
                 */
                sleep(1000);
                clearTpsSendCount();
            }
            tpsSendCountPlus();
            try {
                ChatPostMessageResponse chatPostMessageResponse = send(message, slackChannel.desc());
                boolean result = chatPostMessageResponse.isOk();
                if (result) {
                    return;
                }
            } catch (SlackApiException | IOException e) {
                log.error("SlackException = ", e);
                sleep(30000);
            } catch (Exception e) {
                log.error("Exception =", e);
                sleep(30000);
            }
        }
    }

    private ChatPostMessageResponse send(String message, String channel) throws IOException, SlackApiException {
        
        ChatPostMessageRequest request = ChatPostMessageRequest.builder()
                .channel(channel)
                .text(message)
                .build();
        return methods.chatPostMessage(request);
    }

    private void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            log.error("sleep error", e);
        }
    }

    private synchronized void tpsSendCountPlus() {
        log.info("> Plus");
        ++tpsSendCount;
    }
    private synchronized int getTpsSendCount() {
        log.info("> GET");
        return tpsSendCount;
    }

    private synchronized void clearTpsSendCount() {
        log.info("> Clear");
        tpsSendCount = 0;
    }

}

Slack의 채널을 여러 분기처리하기위해 Enum을 사용하였고, token 및 tps는 config로 관리하였다.

 

해당 개발테스트 중 알게된 사항으로 Slack은 채널당 tps를 1로 엄격히 규정하고있다. 여러 테스트 결과 과도한 트래픽이 초과될시 429 에러가 발생하며 해당 내용은 코드에 정리하였다. 또한, 과도한 트래픽이 발생시 데이터의 유실을 확인하여 Queue를 활용하여 데이터를 안전하게 제공하였다.