Spring Boot Slack 적용(with ThreadPoolTaskExecutor)

최민길(Gale)·2023년 1월 16일
1

Spring Boot 적용기

목록 보기
13/46

안녕하세요 오늘은 에러 메시지를 Slack에서 확인하는 내용에 대해서 포스팅해보도록 하겠습니다. 기존 투다 서비스의 경우 메일로 에러를 확잏나고 있었는데 메일 자동 동기화 속도가 느려 빠르게 받아볼 수 없어서 Slack 연동을 결정했습니다.

우선 Slack에 로그인하신 후 새로운 채널을 만들어 우클릭을 눌러 "채널 세부정보 보기" 항목을 클릭합니다. 그 후 "통합" 탭에 들어가 "앱 추가" 버튼을 누른 후 "imcoming-webhook"을 설치합니다. 그럼 URL을 반환하는데 이 URL을 이용해서 Slack으로 메시지를 전송할 예정입니다.

    // Slack
    implementation "net.gpedro.integrations.slack:slack-webhook:1.4.0"

build.gradle에 다음과 같은 dependency를 추가합니다.

#Slack
slack.webhook-uri={위에서 발급받은 URL}

이어서 application.properties에 위에서 발급받은 URL을 넣어줍니다.

package com.example.test.config;

import net.gpedro.integrations.slack.SlackApi;
import net.gpedro.integrations.slack.SlackAttachment;
import net.gpedro.integrations.slack.SlackMessage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SlackConfig {

    @Value("${slack.webhook-uri}")
    private String slackToken;

    @Bean
    public SlackApi slackApi(){
        return new SlackApi(slackToken);
    }

    @Bean
    public SlackAttachment slackAttachment(){
        SlackAttachment slackAttachment = new SlackAttachment();

        slackAttachment.setFallback("Error");
        slackAttachment.setColor("danger");
        slackAttachment.setTitle("Error Directory");

        return slackAttachment;
    }

    @Bean
    public SlackMessage slackMessage(){
        SlackMessage slackMessage = new SlackMessage();

        slackMessage.setIcon(":ghost:");
        slackMessage.setText("Error Detected");
        slackMessage.setUsername("TODA Error Catcher");
        return slackMessage;
    }

}

다음으로 SlackConfig 클래스를 생성하여 Slack 클래스들의 의존성을 주입하겠습니다.

package com.example.test.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class ThreadPoolConfig {

    // ThreadPool 설정값

    // 서버 코어 수만큼 할당
    @Value("${spring.task.execution.pool.core-size}")
    int CORE_POOL_SIZE;

    // 스레드 풀 최대 스레드 할당 수
    @Value("${spring.task.execution.pool.max-size}")
    int MAX_POOL_SIZE;

    @Bean
    public TaskExecutor taskExecutor(){

        // ThreadPoolTaskExecutor 생성
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.initialize();
        return executor;
    }
}

이어서 ThreadPoolConfig 클래스를 통해 Slack 메시지 발송을 비동기로 처리하도록 설정합니다. Spring Boot에서는 독립적으로 실행한 가능한 작업인 task들을 다양하게 실행하도록 추상화하여 TaskExecutor라는 인터페이스를 제공합니다. 주로 독립적인 스레드에 의해 비동기로 실행될 때 사용하며, ThreadPoolExecutor를 통해 요청을 지정한 크기의 스레드 풀에서 처리하여 무거운 작업을 비동기로 빠르게 처리가 가능합니다.

package com.example.test.error;

import jakarta.servlet.http.HttpServletRequest;
import net.gpedro.integrations.slack.SlackApi;
import net.gpedro.integrations.slack.SlackAttachment;
import net.gpedro.integrations.slack.SlackField;
import net.gpedro.integrations.slack.SlackMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

@ControllerAdvice
public class ErrorDetectAdvisor {

    @Autowired
    private SlackApi slackApi;

    @Autowired
    private SlackAttachment slackAttachment;

    @Autowired
    private SlackMessage slackMessage;

    @Autowired
    private TaskExecutor taskExecutor;

    @ExceptionHandler(Exception.class)
    public void handleException(HttpServletRequest request, Exception e) throws Exception{
        slackAttachment.setTitleLink(request.getContextPath());
        slackAttachment.setText(Arrays.toString(e.getStackTrace()));

        slackAttachment.setFields(
                List.of(
                        new SlackField().setTitle("Request URL").setValue(String.valueOf(request.getRequestURL())),
                        new SlackField().setTitle("Request Method").setValue(request.getMethod()),
                        new SlackField().setTitle("Request Time").setValue(new Date().toString()),
                        new SlackField().setTitle("Request IP").setValue(request.getRemoteAddr()),
                        new SlackField().setTitle("Request User-Agent").setValue(request.getHeader("User-Agent"))
                )
        );

        slackMessage.setAttachments(Collections.singletonList(slackAttachment));

        // ThreadPool에서 비동기로 메시지 발송
        taskExecutor.execute(new Runnable() {
            @Override
            public void run() {
                slackApi.call(slackMessage);
            }
        });
        throw e;
    }
}

이제 Slack으로 메시지 전송 준비는 끝났고, 서비스에서 발생하는 예외를 넘겨주는 세팅을 진행합니다. @ControllerAdvice를 통해서 모든 @Controller에서 발생할 수 있는 예외를 잡아 처리합니다. 그 후 @ExceptionHandler를 통해 @Controller, @RestController가 적용된 Bean내에서 발생하는 예외를 하나의 메서드에서 처리해줍니다. 이 때 내부에 Slack 메시지 발송 준비를 진행하여 slackApi.call(slackMessage) 메소드를 통해 메시지를 발송합니다.

실제로 서비스에서 예외를 발생시키면 다음과 같이 Slack에 메시지가 발송되는 것을 확인할 수 있습니다. 이상으로 오늘의 포스팅 마치도록 하겠습니다!

출처
: https://shanepark.tistory.com/430
: https://jeong-pro.tistory.com/195
: https://hyeonyeee.tistory.com/73

profile
저는 상황에 맞는 최적의 솔루션을 깊고 정확한 개념의 이해를 통한 다양한 방식으로 해결해오면서 지난 3년 동안 신규 서비스를 20만 회원 서비스로 성장시킨 Software Developer 최민길입니다.

0개의 댓글

관련 채용 정보