해당 글에서는 slack-api-client 라이브러리 선정 이유,
@ControllerAdvice에서 슬랙 연동 방법에 대해 다룹니다.
사이드 프로젝트 `모카콩`의 Wiki에 작성한 글에 해당된다.
해당 프로젝트 github: https://github.com/mocacong/Mocacong-Backend
들어가며
모카콩에서는 Slack을 통해 팀원들과 소통하고 있습니다. 개발 관련 회의나 데일리 스크럼, 이슈 등. 모카콩 관련 업무연락은 모두 slack으로 하고 있다고 봐도 무방할 정도입니다. 이는 버그가 발생한 상황에서도 마찬가지인데요.
위 사진처럼 서버 에러라고 추정되는 경우에는, issue 채널에 상황을 공유하곤 했습니다. 이러한 방법에는 몇 가지 단점이 존재합니다.
- 슬랙 이슈 채널에 직접 상황을 공유해야 하는 번거로움
- (다른 파트에서 먼저 발견한 경우) 백엔드 측 에러임에도 불구하고, 백엔드 팀원은 비교적 뒤늦게 확인
위 두 가지의 단점이 크게 느껴져서 internal server error 발생 시에 슬랙으로 알림을 보내주도록 설정했습니다.
라이브러리 선택
MavenRepository를 참고하여 취약점이 없는 라이브러리이자 최근에도 update가 활발하게 진행되고 있는 라이브러리인 slack-api-client를 선택했습니다. 버전은 1.29.0을 선택했습니다.
슬랙 웹훅 설정
Slack에서 서버 에러 로그 알림을 띄워줄 채널을 우클릭하여 아래 프로세스대로 들어가줍니다.
채널 세부정보 보기 → 통합 → 앱 → 앱 추가
`앱 추가`에서 incoming webhook 을 추가해줍시다.
여기서 보이는 웹후크 URL이 스프링 애플리케이션 yml 환경변수로 들어가므로 잘 복사해두도록 합시다.
해당 페이지에서 밑으로 내리면 예시 POST 코드가 있습니다. 복사해서 터미널에 붙여넣기하여 실제로 슬랙에 테스트 알림이 잘 오는지 확인해봅시다.
슬랙에 알림이 잘 온다면 연동이 문제없이 잘 된 것입니다.
스프링 애플리케이션 추가
위에서 살펴본 `slack-api-client:1.29.0` 의존성을 추가해줍시다.
build.gradle
1
2
3
|
dependencies {
implementation 'com.slack.api:slack-api-client:1.29.0'
}
|
cs |
application.yml
slack:
webhook:
url: ${SLACK_WEBHOOK_URL}
아까 환경변수로 주입해야 한다고 했던 웹훅 URL입니다.
`slack.webhook.url` 값으로 해도 되고, 편한대로 `slack.webhook.token` 등 다른 값으로 작성해도 괜찮습니다.
모카콩에서는 서버 에러 발생 시에 ControllerAdvice에서 예외 관련 response를 반환해주고 있습니다.
따라서 Internal Server Error 발생 시 핸들링하는 예외 메서드에 slack api 의존성이 생기게 됩니다.
Slack SDK for Java 홈페이지(https://slack.dev/java-slack-sdk/guides/web-api-basics)의 예시 코드를 살펴보자면 아래와 같습니다.
Slack 객체를 생성하고, 여기에서 send 메서드를 보낼 때 webhookUrl, payload를 첨부해주는 모습입니다.
환경변수는 System.getenv() 메서드로도 가져올 수 있지만, 저는 스프링부트의 편리한 @Value 어노테이션을 이용했습니다.
ControllerAdvice.class
1
2
3
4
|
private final Slack slackClient = Slack.getInstance();
@Value("${slack.webhook.url}")
private String webhookUrl;
|
cs |
payload는 어떻게 작성하는 것이 좋을까요?
https://slack.dev/java-slack-sdk/guides/incoming-webhooks 에 간단한 작성법이 설명돼있습니다.
Payload.builder() 또는 직접 json 형태로 텍스트를 작성하여 payload를 만든 후, slack.send(webhookUrl, payload) 로 보내줄 수도 있습니다. 하지만, Java8의 함수형 프로그래밍을 이용한다면 더 간편하게 이용이 가능합니다.
하지만 단순히 text만 담아서 보내주는 것은 가독성이 그리 좋지 않다고 판단했습니다. 단순 Error의 getMessage() 값만 텍스트로 담아서 슬랙에 알림을 보내준 예시 화면은 아래와 같습니다.
위 알림만 봐서는 어떠한 에러인지, 어느 URL에서 발생했는지, 위급한 에러인지 한눈에 알기가 어렵습니다.
위처럼 좀 더 세부적인 정보를 예쁘게 담고, 빨간색으로 위험성을 부각시켜주게 하려면 어떻게 해야 할까요?
slack-api-client에는 text 뿐만 아니라 Attachment, Field도 존재합니다.
위 화면에서 빨간색 라인이 하나의 Attachment에 해당되고, Request URL, Error Message 등의 영역이 한 Field에 해당된다고 보시면 됩니다.
더 자세한 내용은 https://api.slack.com/reference/messaging/attachments#example 를 참고해주세요.
위 화면처럼 보이게 조금 더 꾸며보겠습니다.
ControllerAdvice.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> unhandledException(Exception e, HttpServletRequest request) {
log.error("UnhandledException: {} {} errMessage={}\n",
request.getMethod(),
request.getRequestURI(),
e.getMessage()
);
sendSlackAlertErrorLog(e, request); // 슬랙 알림 보내는 메서드
return ResponseEntity.internalServerError()
.body(new ErrorResponse(9999, "일시적으로 접속이 원활하지 않습니다. 모카콩 서비스 팀에 문의 부탁드립니다."));
}
// 슬랙 알림 보내는 메서드
private void sendSlackAlertErrorLog(Exception e, HttpServletRequest request) {
try {
slackClient.send(webhookUrl, payload(p -> p
.text("서버 에러 발생! 백엔드 측의 빠른 확인 요망")
// attachment는 list 형태여야 합니다.
.attachments(
List.of(generateSlackAttachment(e, request))
)
));
} catch (IOException slackError) {
// slack 통신 시 발생한 예외에서 Exception을 던져준다면 재귀적인 예외가 발생합니다.
// 따라서 로깅으로 처리하였고, 모카콩 서버 에러는 아니므로 `error` 레벨보다 낮은 레벨로 설정했습니다.
log.debug("Slack 통신과의 예외 발생");
}
}
// attachment 생성 메서드
private Attachment generateSlackAttachment(Exception e, HttpServletRequest request) {
String requestTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(LocalDateTime.now());
String xffHeader = request.getHeader("X-FORWARDED-FOR"); // 프록시 서버일 경우 client IP는 여기에 담길 수 있습니다.
return Attachment.builder()
.color("ff0000") // 붉은 색으로 보이도록
.title(requestTime + " 발생 에러 로그")
// Field도 List 형태로 담아주어야 합니다. .fields(List.of(
generateSlackField("Request IP", xffHeader == null ? request.getRemoteAddr() : xffHeader),
generateSlackField("Request URL", request.getRequestURL() + " " + request.getMethod()),
generateSlackField("Error Message", e.getMessage())
)
)
.build();
}
// Field 생성 메서드
private Field generateSlackField(String title, String value) {
return Field.builder()
.title(title)
.value(value)
.valueShortEnough(false)
.build();
}
|
cs |
최종 결과
최종적으로 알림이 문제없이 잘 보내지는 것을 확인할 수 있습니다.
'JAVA > JAVA | Spring 학습기록' 카테고리의 다른 글
[Spring] 이벤트 발행 및 @TransactionalEventListener을 이용한 의존성 줄이기 (2) | 2023.04.25 |
---|---|
[230422] Spring Camp 2023 갔다온 후기 (6) | 2023.04.22 |
[Spring] FeignClient를 이용한 Apple OAuth 구현 일지 (2) (0) | 2023.04.15 |
[Spring] FeignClient를 이용한 Apple OAuth 구현 일지 (1) (5) | 2023.04.11 |
[ERROR] 인터셉터 사용 환경에서 Swagger의 Failed to load remote configuration 해결법 (2) | 2023.03.27 |