Monitoring

[MONITORING] 예외발생 시 Slack 으로 알림 보내기

KJihun 2026. 4. 1. 16:26
728x90

1. 코드작성

public enum ErrorLevel {
    LOW(Level.INFO, false),
    MEDIUM(Level.WARN, false),
    HIGH(Level.ERROR, true),
    CRITICAL(Level.ERROR, true);
}

- 에러레벨 ENUM으로 작성

 

if (level.isSlackNotify()) slackNotifier.send(e, request);

- true일 경우 심각한 예외로 지정, slack에 메세지 전달

 

 

public class SlackNotifier {

    @Value("${slack.webhook.url:}")
    private String webhookUrl;

    private final RestClient restClient = RestClient.create();

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Async
    public void send(Exception e, HttpServletRequest request) {
        if (webhookUrl == null || webhookUrl.isBlank()) {
            log.warn("Slack webhook URL이 설정되지 않아 알림을 보내지 않습니다.");
            return;
        }

        try {
            ErrorLevel level = (e instanceof BaseException be) ? be.getErrorCode().getLevel() : ErrorLevel.CRITICAL;
            String emoji = (level == ErrorLevel.CRITICAL) ? ":rotating_light:" : ":warning:";
            String mention = (level == ErrorLevel.CRITICAL) ? "<!channel> " : "";
            String errorCode = (e instanceof BaseException be) ? be.getErrorCode().name() : "INTERNAL_ERROR";

            String stackTrace = getStackTrace(e, 10);

            String payload = """
                    {
                      "blocks": [
                        {
                          "type": "header",
                          "text": {
                            "type": "plain_text",
                            "text": "%s [%s] 예외 발생"
                          }
                        },
                        {
                          "type": "section",
                          "fields": [
                            { "type": "mrkdwn", "text": "*Error Code:*\\n`%s`" },
                            { "type": "mrkdwn", "text": "*Level:*\\n`%s`" },
                            { "type": "mrkdwn", "text": "*URI:*\\n`%s %s`" },
                            { "type": "mrkdwn", "text": "*Time:*\\n`%s`" }
                          ]
                        },
                        {
                          "type": "section",
                          "text": {
                            "type": "mrkdwn",
                            "text": "*Message:*\\n```%s```"
                          }
                        },
                        {
                          "type": "section",
                          "text": {
                            "type": "mrkdwn",
                            "text": "*StackTrace:*\\n```%s```"
                          }
                        }
                      ],
                      "text": "%s%s [%s] %s"
                    }
                    """.formatted(
                    emoji, level,
                    errorCode,
                    level,
                    request.getMethod(), request.getRequestURI(),
                    LocalDateTime.now().format(FORMATTER),
                    escapeJson(e.getMessage()),
                    escapeJson(stackTrace),
                    mention, emoji, level, escapeJson(e.getMessage())
            );

            restClient.post()
                    .uri(webhookUrl)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(payload)
                    .retrieve()
                    .toBodilessEntity();

            log.info("Slack 알림 발송 완료 [{}]", level);
        } catch (Exception ex) {
            log.error("Slack 알림 발송 실패", ex);
        }
    }

    private String getStackTrace(Exception e, int maxLines) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        String[] lines = sw.toString().split("\n");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < Math.min(lines.length, maxLines); i++) {
            sb.append(lines[i]).append("\n");
        }
        if (lines.length > maxLines) {
            sb.append("... ").append(lines.length - maxLines).append(" more lines");
        }
        return sb.toString().trim();
    }

    private String escapeJson(String text) {
        if (text == null) return "null";
        return text
                .replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\n", "\\n")
                .replace("\r", "\\r")
                .replace("\t", "\\t");
    }

- 메세지 전송 포맷 설정 및 알림 보내기

 

 

 

2. Slack 워크스페이스 생성

  1. https://slack.com/create 접속
  2. 이메일 입력 → 인증코드 확인
  3. 워크스페이스 이름 입력 (예: dkdk-team)
  4. 알림용 채널 생성 (예: #서버-알림)

 

 

 

 

 

 

 


1. Slack App 생성 + Webhook 발급

  1. https://api.slack.com/apps → Create New App → From scratch
  2. App 이름 입력 (예: dkdk-alert), 위에서 만든 워크스페이스 선택


  3. Incoming Webhooks → 토글 On  → Add New Webhook

 


  5. 생성된 Webhook URL 복사

  6. 설정에 붙여넣기
  slack.webhook.url=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX

'Monitoring' 카테고리의 다른 글

[MONITORING] Prometheus + Grafana + docker  (0) 2026.03.31