diff --git a/backend/src/main/java/com/yigongil/backend/ui/exceptionhandler/ApiExceptionHandler.java b/backend/src/main/java/com/yigongil/backend/ui/exceptionhandler/ApiExceptionHandler.java index 3315e7af..6e06cdf2 100644 --- a/backend/src/main/java/com/yigongil/backend/ui/exceptionhandler/ApiExceptionHandler.java +++ b/backend/src/main/java/com/yigongil/backend/ui/exceptionhandler/ApiExceptionHandler.java @@ -1,9 +1,19 @@ package com.yigongil.backend.ui.exceptionhandler; +import static com.slack.api.webhook.WebhookPayloads.payload; + +import com.slack.api.Slack; +import com.slack.api.model.Attachment; +import com.slack.api.model.Field; import com.yigongil.backend.exception.HttpException; import io.jsonwebtoken.ExpiredJwtException; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; import javax.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -14,7 +24,10 @@ @RestControllerAdvice public class ApiExceptionHandler { + @Value("${slack.webhook-url}") + private String webhookUrl; private final InternalServerErrorMessageConverter messageConverter; + private final Slack slackClient = Slack.getInstance(); public ApiExceptionHandler(InternalServerErrorMessageConverter messageConverter) { this.messageConverter = messageConverter; @@ -22,6 +35,7 @@ public ApiExceptionHandler(InternalServerErrorMessageConverter messageConverter) @ExceptionHandler public ResponseEntity handleHttpException(HttpException e, HttpServletRequest request) { + sendSlackAlertErrorLog(e, request); log.error("예외 발생: ", e); return ResponseEntity.status(e.getHttpStatus()) .body(e.getMessageWithInput()); @@ -29,6 +43,7 @@ public ResponseEntity handleHttpException(HttpException e, HttpServletRe @ExceptionHandler public ResponseEntity handleExpiredJwt(ExpiredJwtException e, HttpServletRequest request) { + sendSlackAlertErrorLog(e, request); log.info("토큰 만료: ", e); return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body("토큰이 만료되었습니다"); @@ -36,6 +51,7 @@ public ResponseEntity handleExpiredJwt(ExpiredJwtException e, HttpServle @ExceptionHandler public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpServletRequest request) { + sendSlackAlertErrorLog(e, request); log.error(e.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(e.getMessage()); @@ -43,8 +59,46 @@ public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotVali @ExceptionHandler public ResponseEntity handleException(Exception e, HttpServletRequest request) { + sendSlackAlertErrorLog(e, request); log.error("예상치 못한 예외 발생: ", e); return ResponseEntity.internalServerError() .body(messageConverter.convert(e)); } + + + private void sendSlackAlertErrorLog(Exception e, HttpServletRequest request) { + try { + slackClient.send(webhookUrl, payload(p -> p + .text("서버 에러 발생") + .attachments( + List.of(generateSlackAttachment(e, request)) + ) + )); + } catch (IOException error) { + log.info("Slack 통신 예외 발생"); + } + } + + 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"); + return Attachment.builder() + .color("ff0000") + .title(requestTime + " 발생 에러 로그") + .fields(List.of( + generateSlackField("Request IP", xffHeader == null ? request.getRemoteAddr() : xffHeader), + generateSlackField("Request URL", request.getRequestURL() + " " + request.getMethod()), + generateSlackField("Error Message", e.getMessage()) + ) + ) + .build(); + } + + private Field generateSlackField(String title, String value) { + return Field.builder() + .title(title) + .value(value) + .valueShortEnough(false) + .build(); + } } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 23dd05aa..b9e5039d 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -59,3 +59,6 @@ jwt: swagger: server-url: "https://localhost:8080/v1" + +slack: + webhook-url: http:locallocalclocal diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml index 7fdfd853..6d8ebb13 100644 --- a/backend/src/test/resources/application.yml +++ b/backend/src/test/resources/application.yml @@ -41,3 +41,6 @@ oauth2: jwt: expiration: 5 key: qkljwnqdjnqjkdnqdwkdkm31i4j1dasksmdasdsadsasdafef113 + +slack: + webhook-url: http://ignore