[Spring] ControllerAdvice 웹훅 연결해서 알림받기

easyone·2025년 11월 18일

Spring

목록 보기
14/18

UMC 7주차 시니어 미션입니다.

환경변수 설정

운영 서버

app:
  notification:
    discord:
      webhook-url: ${DISCORD_WEBHOOK_URL}
      enabled: true
    slack:
      webhook-url: ${SLACK_WEBHOOK_URL}
      enabled: false
    alert-env: prod

개발 서버

app:
  notification:
    discord:
      webhook-url: ${DISCORD_WEBHOOK_URL}
      enabled: true
    slack:
      webhook-url: ${SLACK_WEBHOOK_URL}
      enabled: false
    alert-env: dev

NotificationService 구현

1. 500에러 감지

    public void notifyError(String requestUrl, Exception exception, String method) {
        // 현재 환경이 알림 대상 환경인지 확인
        if (!isAlertTargetEnv()) {
            log.debug("Alert is disabled for environment: {}", currentEnv);
            return;
        }

        String errorMessage = formatErrorMessage(requestUrl, exception, method);

        if (discordEnabled && !discordWebhookUrl.isBlank()) {
            sendToDiscord(errorMessage);
        }

        if (slackEnabled && !slackWebhookUrl.isBlank()) {
            sendToSlack(errorMessage);
        }
    }

2. Discord/Slack으로 에러메세지를 전송하는 메서드

    /**
     * Discord로 에러 메시지 전송
     */
    private void sendToDiscord(String errorMessage) {
        try {
            Map<String, Object> payload = new HashMap<>();
            payload.put("content", errorMessage);

            restTemplate.postForObject(discordWebhookUrl, payload, String.class);
            log.info("Discord notification sent successfully");
        } catch (RestClientException e) {
            log.error("Failed to send Discord notification", e);
        } catch (Exception e) {
            log.error("Unexpected error while sending Discord notification", e);
        }
    }

    /**
     * Slack으로 에러 메시지 전송
     */
    private void sendToSlack(String errorMessage) {
        try {
            Map<String, Object> payload = new HashMap<>();
            Map<String, Object> attachments = new HashMap<>();

            attachments.put("color", "danger");
            attachments.put("text", errorMessage);

            payload.put("attachments", new Object[]{attachments});

            restTemplate.postForObject(slackWebhookUrl, payload, String.class);
            log.info("Slack notification sent successfully");
        } catch (RestClientException e) {
            log.error("Failed to send Slack notification", e);
        } catch (Exception e) {
            log.error("Unexpected error while sending Slack notification", e);
        }
    }

3. 에러 메세지를 포맷팅


    /**
     * 에러 메시지 포맷팅
     */
    private String formatErrorMessage(String requestUrl, Exception exception, String method) {
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        String exceptionName = exception.getClass().getSimpleName();
        String exceptionMessage = exception.getMessage() != null ? exception.getMessage() : "No message";
        
        // 스택 트레이스의 첫 번째 라인 추출
        String stackTrace = "";
        if (exception.getStackTrace().length > 0) {
            StackTraceElement element = exception.getStackTrace()[0];
            stackTrace = String.format("%s.%s (Line %d)", 
                    element.getClassName(), element.getMethodName(), element.getLineNumber());
        }

        return String.format(
                "[500 ERROR ALERT]\n" +
                "====================================\n" +
                "Time: %s\n" +
                "Environment: %s\n" +
                "Request URL: %s\n" +
                "Method: %s\n" +
                "Exception: %s\n" +
                "Message: %s\n" +
                "Location: %s\n" +
                "====================================",
                now.format(formatter),
                currentEnv.toUpperCase(),
                requestUrl,
                method,
                exceptionName,
                exceptionMessage,
                stackTrace
        );
    }

4. 현재 환경이 알림 대상인지 확인

    /**
     * 현재 환경이 알림 대상인지 확인
     */
    private boolean isAlertTargetEnv() {
        if (alertEnv == null || alertEnv.isBlank()) {
            return false;
        }
        String[] envs = alertEnv.split(",");
        for (String env : envs) {
            if (env.trim().equals(currentEnv)) {
                return true;
            }
        }
        return false;
    }

ExceptionAdvice 파일 수정

 private final NotificationService notificationService;

500 에러를 처리하는 로직에서 알림 전송 로직을 추가한다.

    // 모든 미처리 예외 → 500 
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleUnknownException(Exception e, HttpServletRequest request) {
        log.error("Unhandled exception", e);

        // 요청 정보 추출
        String requestUrl = request.getRequestURI();
        String method = request.getMethod();

        // Discord/Slack으로 알림 전송
        notificationService.notifyError(requestUrl, e, method);

        ApiResponse<Object> body = ApiResponse.onFailure(ErrorCode.INTERNAL_SERVER_ERROR, null);
        WebRequest webRequest = new ServletWebRequest(request);
        return handleExceptionInternal(e, body, new HttpHeaders(),
                ErrorCode.INTERNAL_SERVER_ERROR.getHttpStatus(), webRequest);
    }
profile
백엔드 개발자 지망 대학생

0개의 댓글