안녕하세요 오늘은 에러 메시지를 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