[Spring Boot] 디스코드를 활용한 에러 알림

이정진·2024년 6월 3일
1

개발

목록 보기
11/21
post-thumbnail

공작소는 Prometheus + Grafana 조합을 구성해서 에러 모니터링 시스템을 구축되어 있다.
일반적으로 모니터링 시스템은 API 서버와 분리하여 구축해놓아야 하지만, 서버 비용 등의 부담으로 인해 단일 EC2 내에 컨테이너로 띄워져 있었는데, 이로 인해 Docker 내에 여유 공간이 없는 문제가 발생해 현재는 Prometheus 컨테이너는 중단해놓은 상황이다.

위와 같은 상황으로 인해, 실제 사용 중에 발생한 문제를 실시간으로 인지할 수 있도록 기존 팀 내 소통 채널인 디스코드를 활용해 에러 알림을 받기로 결정했다.

Webhook

디스코드는 웹훅 방식으로 채널에 메시지를 발송할 수 있다.

웹훅에 대해서 간단하게 정리해보자면, 데이터가 변경되었을 때 실시간으로 알림을 받을 수 있는 기능으로, 웹 서비스의 이벤트 데이터를 전달하는 HTTP 기반 콜백 함수이다.

웹훅은 서버에서 특정 이벤트가 발생했을 때, 클라이언트를 호출하는 방식이기에 역방향 API로 부를 수 있을 것 같다.


(이미지 출처: 토스페이먼츠 기술 블로그 / 2024-06-02)

Spring Boot + Discord

  1. 채널 생성

  2. 채널 설정 -> 연동 -> 웹후크 생성

  3. 생성 후, 웹후크 URL 복사

위 과정까지 진행하면, 더 이상 디스코드 내에서 할 작업은 없다. 이제는 Spring Boot 서버에서 작업을 진행하면 된다.

  1. application.yml 설정
discord:
  environment: dev
  webhook-url: https://discord.com/api/webhooks/{webhook_id}/{webhook_token}

위의 webook-url은 3번 과정에서 복사한 url을 기입하면 된다.
environment 변수는 dev/prod 중 하나의 값이며, 배포된 환경에서만 알림을 발송하기 위하여 해당 값이 prod인지 확인하는 조건 분기 처리 시 사용한다.

  1. 디스코드 발송 시 활용하는 구조 TEST

다만, 나는 위의 '공작소 API Server'라는 문구는 불필요하다고 판단해서 삭제했다. 해당 문구를 삭제한 코드는 아래 과정을 보면 된다.

  1. dependency 추가

디스코드는 RESTful API 형식으로 Webhook을 활용해 메시지를 발송할 수 있다.
Spring Boot에서는 RestTemplate, WebClient, OpenFeign 등을 HTTP Client 모듈로 활용할 수 있는데, 나는 비동기와 논블럭의 강점을 가진 WebClient를 선택했다.

implementation 'org.springframework.boot:spring-boot-starter-webflux'

WebClient는 Webflux 내에 존재하기에 위와 같이 Webflux를 dependency에 추가했다.

  1. 디스코드 발송 컴포넌트
@Component
public class DiscordClient {

    @Value("${discord.environment}")
    private String environment;

    @Value("${discord.webhook-url}")
    private String webhookUrl;

    public void sendErrorMessage(Integer code, String message, String stackTrace) {
        if(!environment.equals("prod")) {
            return;
        }

        WebClient webClient = WebClient.create();

        //요청 본문
        Map<String, Object> embedData = new HashMap<>();

        embedData.put("title", "공작소 서버 에러 발생");

        Map<String, String> field1 = new HashMap<>();
        field1.put("name", "발생시각");
        field1.put("value", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));

        Map<String, String> field2 = new HashMap<>();
        field2.put("name", "에러 코드");
        field2.put("value", code.toString());

        Map<String, String> field3 = new HashMap<>();
        field3.put("name", "에러 명");
        field3.put("value", message);

        Map<String, String> field4 = new HashMap<>();
        field4.put("name", "스택 트레이스");
        field4.put("value", stackTrace);

        embedData.put("fields", List.of(field1, field2, field3, field4));

        Map<String, Object> payload = new HashMap<>();
        payload.put("embeds", new Object[]{embedData});

        webClient.post()
                .uri(webhookUrl)
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(payload)
                .retrieve()
                .bodyToMono(Void.class)
                .block();
    }
}

코드를 보면, 배포 환경이 아니라면 비즈니스 로직을 진행하지 않고 메소드를 반환하는 것을 확인할 수 있다.

결과

이제, 배포된 환경에서 오류가 발생하면 실시간으로 알림을 받아볼 수 있다.


레퍼런스

0개의 댓글