스프링부트 예외 발생 시, Slack으로 알림 보내기 (feat. Slack WebHooks)
스프링부트 with Slack
슬랙 WebHooks 사용하여 에러 로깅해보기
서버를 개발하다보면 미처 처리하지 못한 알 수 없는 에러가 발생하는 경우가 종종 발생한다.
이런 경우 로컬 환경이라면 금방 디버깅이 가능하지만, 서버를 배포해놓은 상황이라면
서버의 로그 파일을 통해 발생한 예외를 확인하거나, 모니터링 기능을 추가하여 해결하는 등 다양한 방법을 통해 해결해야한다.
이와 마찬가지로 슬랙을 통해서도 문제에 대한 해결책을 던져볼 수 있다.
예외가 발생하면 슬랙의 채널에 에러 로그가 전달되어 환경에 구분없이 빠른 조치와 해결이 가능하다!
꼭 한번 사용해보는 것을 추천한다.
패키지 구조
플로우 🌊
- SlackTestController에서 요청을 받아 Exception을 발생시킨다.
- ControllerExceptionAdvice에서 예외를 캐치하여 처리되어있지 않은 에러라면 500에러를 발생시키고 ApiResponse를 return함과 동시에,
SlackApi를 사용하여 발생한 예외에 대한 내용을 작성하여 Slack webhooks 를 통해 설정한 채널로 전송 - 받은 예외에 대한 내용을 통하여 지정한 슬랙 채널에 에러 로깅을 수행
- 에러 로깅을 통한 트러블슈팅
1. 슬랙에 Webhook 앱 추가 ➕
슬랙에 요청이 들어오면 특정 채널에 전달받은 요청에 담긴 메시지를 전달하기 위하여 Webhook 앱을 설치해야 한다.
webhook 추가를 원하는 슬랙 워크스페이스에 접속하여 좌측 채널 탭, 가장 하단에 있는 앱 추가 버튼 클릭
우리는 요청을 수신받아 메시지를 출력해줄 것이기 때문에 Incoming WebHooks 추가
Slack에 추가 버튼을 클릭한 후, WebHook URL을 통해 보낸 메시지를 출력하기 원하는 채널을 선택
설정이 끝나면 WebHooks URL을 받을 수 있다.
2. SpringBoot에 적용 ✨
2-1. build.gradle에 의존성 추가
// Slack Webhook
implementation 'com.slack.api:slack-api-client:1.28.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.slack.api:slack-app-backend:1.28.0'
implementation 'com.slack.api:slack-api-model:1.28.0'
2-2. application.yml 에 slack 관련 환경설정
slack:
webhook:
url: {webhook-URL}
2-3. RestController 생성
@RestController
@RequestMapping("/test")
public class SlackTestController {
@GetMapping
@ResponseStatus(HttpStatus.OK)
public ApiResponse test() {
throw new IllegalArgumentException();
}
}
/test 라는 경로로 요청이 들어오면 IllegalArgumentException 을 발생시키는 API 생성
2-4. Error처리 Enum 클래스 생성
Error.java
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum Error {
/**
* 500 INTERNAL SERVER ERROR
*/
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류"),
;
private final HttpStatus httpStatus;
private final String message;
public int getHttpStatusCode() {
return httpStatus.value();
}
}
상태코드와 ErrorMessage에 대한 정보를 담은 Error enum클래스 생성
필드로는 HttpStatus를 담는 httpStatus와 String형태의 message가 있다.
2-5. 요청에 대한 응답결과 Response를 위한 ApiResponse 클래스 생성
@JsonInclude(JsonInclude.Include.NON_NULL)
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ApiResponse<T> {
private final int status;
private final String message;
private T data;
public static ApiResponse error(Error error) {
return new ApiResponse<>(error.getHttpStatusCode(), error.getMessage());
}
}
위에서 생성한 Error enum클래스를 입력받아, 이를 통해 응답결과를 생성해줄 ApiResponse 클래스이며 정적 메서드로 구성된다.
Error 외에도 요청 성공에 대한 응답도 추가하여 사용가능하다.
2-6. 예외를 catch해줄 ControllerExceptionAdvice 클래스 생성
@RestControllerAdvice
@Component
@RequiredArgsConstructor
public class ControllerExceptionAdvice {
private final SlackUtil slackUtil;
/**
* 500 Internal Server
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
protected ApiResponse<Object> handleException(final Exception error, final HttpServletRequest request) throws IOException {
slackUtil.sendAlert(error,request);
return ApiResponse.error(Error.INTERNAL_SERVER_ERROR);
}
}
@RestControllerAdvice는 스프링에서 제공하는 기능으로 요청에 대하여 @ExceptionHandler에 등록해놓은 예외가 발생하면 해당 예외를 잡은 핸들러 메서드를 실행한다.
이처럼 이곳에서 서버 개발자 측에서 예상한 예외들을 핸들링할 수 있는데, 예상한 예외상황에서 벗어나 예외 처리가 되지 않은 내용에 대해서는
예외 클래스의 최상위 클래스인 Exception 클래스에 대한 handleException 메서드가 이를 캐치하게 되고 그로 인해 slackApi의 에러 로깅 메서드를 실행시키는 것이다.
2-7. Slack 에 에러 로깅을 수행하는 SlackApi 클래스 생성
@Component
@RequiredArgsConstructor
@Slf4j
public class SlackUtil {
//application.yml 에 등록해놓은 webhookUrl
@Value("${slack.webhook.url}")
private String webhookUrl;
private StringBuilder sb= new StringBuilder();
// Slack으로 알림 보내기
public void sendAlert(Exception error, HttpServletRequest request) throws IOException {
// 현재 프로파일이 특정 프로파일이 아니면 알림보내지 않기
// if (!env.getActiveProfiles()[0].equals("set1")) {
// return;
// }
// 메시지 내용인 LayoutBlock List 생성
ListlayoutBlocks= generateLayoutBlock(error,request);
// 슬랙의 send API과 webhookURL을 통해 생성한 메시지 내용 전송
Slack.getInstance().send(webhookUrl, WebhookPayloads
.payload(p->
// 메시지 전송 유저명
p.username("Exception is detected 🚨")
// 메시지 전송 유저 아이콘 이미지 URL
.iconUrl("<https://yt3.googleusercontent.com/ytc/AGIKgqMVUzRrhoo1gDQcqvPo0PxaJz7e0gqDXT0D78R5VQ=s900-c-k-c0x00ffffff-no-rj>")
// 메시지 내용
.blocks(layoutBlocks)));
}
// 전체 메시지가 담긴 LayoutBlock 생성
private List generateLayoutBlock(Exception error, HttpServletRequest request) {
return Blocks.asBlocks(
getHeader("서버 측 오류로 예상되는 예외 상황이 발생하였습니다."),
Blocks.divider(),
getSection(generateErrorMessage(error)),
Blocks.divider(),
getSection(generateErrorPointMessage(request)),
Blocks.divider(),
// 이슈 생성을 위해 프로젝트의 Issue URL을 입력하여 바로가기 링크를 생성
getSection("<github_issue_url)|이슈 생성하러="" 가기="">")
);
}
// 예외 정보 메시지 생성
private String generateErrorMessage(Exception error) {
sb.setLength(0);
sb.append("*[🔥 Exception]*" + NEW_LINE + error.toString() + DOUBLE_NEW_LINE);
sb.append("*[📩 From]*" + NEW_LINE + readRootStackTrace(error) + DOUBLE_NEW_LINE);
return sb.toString();
}
// HttpServletRequest를 사용하여 예외발생 요청에 대한 정보 메시지 생성
private String generateErrorPointMessage(HttpServletRequest request) {
sb.setLength(0);
sb.append("*[🧾세부정보]*" + NEW_LINE);
sb.append("Request URL : " + request.getRequestURL().toString() + NEW_LINE);
sb.append("Request Method : " + request.getMethod() + NEW_LINE);
sb.append("Request Time : " + new Date() + NEW_LINE);
return sb.toString();
}
// 예외발생 클래스 정보 return
private String readRootStackTrace(Exception error) {
return error.getStackTrace()[0].toString();
}
// 에러 로그 메시지의 제목 return
private LayoutBlock getHeader(String text) {
return Blocks.header(h->h.text(
plainText(pt->pt.emoji(true)
.text(text))));
}
// 에러 로그 메시지 내용 return
private LayoutBlock getSection(String message) {
return Blocks.section(s->
s.text(BlockCompositions.markdownText(message)));
}
</github_issue_url)|이슈>
해당 클래스에서는 Slack API를 사용하여 슬랙 채널로 전송할 메시지를 생성 및 전송한다.
자세한 내용은 주석을 통해 알아보자!
짚고 넘어가기🔥
현재 SlackApi 클래스에서 메시지 내용을 입력받아 LayoutBlock을 return하는 getSection, getHeader 등의 메시지를 통해
슬랙 메시지가 LayoutBlock 단위로 구성되는 것을 알 수 있다!
블럭을 구성하기 위해선 의존성에 추가했던 slack.api.model 에서 제공하는
Blocks 클래스의 정적 메서드를 사용하여 구현이 가능하다.
슬랙에서 제공하는 Block Kit Builder 를 사용하면 어떤 블럭이 존재하는 지, 이름과 형태를 확인할 수 있다.
이를 활용하여 원하는 블럭을 구성하여 메시지를 만들어보자
3. 테스트 ✅
이제 모든 설정이 끝났으니 예외 발생 시, 실제 슬랙 채널로 에러 로깅 메시지가 전송되는 지 확인하자
Postman을 통한 결과는 성공적으로 출력되었으며 상태코드 500이 return 되었다.
슬랙에도 마찬가지로 위와 같이 정상적으로 예외에 대한 정보를 담은 메시지가 출력되었다!