-
Notifications
You must be signed in to change notification settings - Fork 0
모든 Request에 대해 로깅을 적용한다. #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
80cda16
c439bf1
9e3a755
0378dc5
9146dda
7058e16
152d88f
e708045
55631d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||||||||||||||
| package akuma.whiplash.global.config.sentry; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import io.sentry.SentryOptions; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.context.annotation.Bean; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.context.annotation.Configuration; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Configuration | ||||||||||||||||||||||||||||||||||||||||
| public class SentryConfig { | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Bean | ||||||||||||||||||||||||||||||||||||||||
| public SentryOptions.BeforeSendCallback beforeSendCallback() { | ||||||||||||||||||||||||||||||||||||||||
| return (event, hint) -> { | ||||||||||||||||||||||||||||||||||||||||
| if (event.getExceptions() != null && !event.getExceptions().isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||
| var ex = event.getExceptions().get(0); | ||||||||||||||||||||||||||||||||||||||||
| if (ex.getType() != null && ex.getType().endsWith("ApplicationException")) { | ||||||||||||||||||||||||||||||||||||||||
| // 전역 핸들러에서 미리 심어둔 태그 | ||||||||||||||||||||||||||||||||||||||||
| String code = event.getTag("error.code"); | ||||||||||||||||||||||||||||||||||||||||
| if (code != null) { | ||||||||||||||||||||||||||||||||||||||||
| String originalMessage = ex.getValue(); // 기존 사람이 읽는 메시지 | ||||||||||||||||||||||||||||||||||||||||
| // 타이틀에 반영되는 'type'을 에러코드로 교체 | ||||||||||||||||||||||||||||||||||||||||
| ex.setType(code); | ||||||||||||||||||||||||||||||||||||||||
| // 부제목(value)은 원래 메시지를 유지(또는 필요 시 축약) | ||||||||||||||||||||||||||||||||||||||||
| ex.setValue(originalMessage); | ||||||||||||||||||||||||||||||||||||||||
| // 참고용으로 원본도 extra에 보존 | ||||||||||||||||||||||||||||||||||||||||
| event.setExtra("original.message", originalMessage); | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+17
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 컴파일 오류: event.getTag(...)는 존재하지 않습니다. getTags().get(...)을 사용하세요. SentryEvent에는 getTag(String)가 없고, Map 형태의 getTags()만 제공합니다. 현재 코드는 컴파일에 실패합니다. 아래처럼 수정하세요(Null 안전 포함): - String code = event.getTag("error.code");
+ var tags = event.getTags();
+ String code = (tags != null) ? tags.get("error.code") : null;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| return event; | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,14 +1,18 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package akuma.whiplash.global.exception; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import akuma.whiplash.global.log.LogUtils; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import akuma.whiplash.global.response.ApplicationResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import akuma.whiplash.global.response.code.BaseErrorCode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import akuma.whiplash.global.response.code.CommonErrorCode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import io.sentry.Sentry; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.http.HttpServletRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.validation.ConstraintViolation; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.validation.ConstraintViolationException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.LinkedHashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Optional; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
4
to
16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Sentry 연동 시 유용 태그/유틸 의존성 추가 필요
import akuma.whiplash.global.response.ApplicationResponse;
import akuma.whiplash.global.response.code.BaseErrorCode;
import akuma.whiplash.global.response.code.CommonErrorCode;
+import akuma.whiplash.global.log.LogConst;
+import akuma.whiplash.global.log.LogUtils;
import io.sentry.Sentry;
import jakarta.servlet.http.HttpServletRequest;
...
import java.util.stream.Collectors;
+import org.slf4j.MDC;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.http.HttpHeaders; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.http.HttpStatus; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,6 +49,14 @@ public ResponseEntity<Object> handleMethodArgumentNotValid( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendErrorToSentry( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| extractRequestUri(request), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogUtils.maskSensitiveQuery(extractQueryString(request)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.getCustomCode(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.getHttpStatus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return handleExceptionInternalArgs( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -59,6 +71,18 @@ public ResponseEntity<Object> validation(ConstraintViolationException e, WebRequ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .findFirst() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orElseThrow(() -> new RuntimeException("ConstraintViolationException Error")); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendErrorToSentry( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| extractRequestUri(request), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogUtils.maskSensitiveQuery( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e.getConstraintViolations().stream() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(v -> v.getPropertyPath() + "=" + v.getInvalidValue()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .collect(Collectors.joining(", ")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorMessage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.getHttpStatus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return handleExceptionInternalConstraint(e, CommonErrorCode.valueOf(errorMessage), request); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -74,13 +98,30 @@ protected ResponseEntity<Object> handleHttpMessageNotReadable( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.getCustomCode(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.getMessage() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendErrorToSentry( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ex, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| extractRequestUri(request), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogUtils.maskSensitiveQuery(extractQueryString(request)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.getCustomCode(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.getHttpStatus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @ExceptionHandler | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public ResponseEntity<Object> exception(Exception e, WebRequest request) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.error("Unexpected error: ", e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendErrorToSentry( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.getDescription(false), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.getParameterMap().toString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CommonErrorCode.INTERNAL_SERVER_ERROR.getCustomCode(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CommonErrorCode.INTERNAL_SERVER_ERROR.getHttpStatus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return handleExceptionInternalFalse( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CommonErrorCode.INTERNAL_SERVER_ERROR.getHttpStatus(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -90,10 +131,18 @@ public ResponseEntity<Object> exception(Exception e, WebRequest request) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @ExceptionHandler(value = ApplicationException.class) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public ResponseEntity<Object> onThrowException(ApplicationException applicationException, HttpServletRequest request) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BaseErrorCode baseErrorCode = applicationException.getCode(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public ResponseEntity<Object> onThrowException(ApplicationException ex, HttpServletRequest request) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BaseErrorCode baseErrorCode = ex.getCode(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return handleExceptionInternal(applicationException, baseErrorCode, null, request); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendErrorToSentry( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ex, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.getRequestURI(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogUtils.maskSensitiveQuery(request.getQueryString()), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| baseErrorCode.getCustomCode(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| baseErrorCode.getHttpStatus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return handleExceptionInternal(ex, baseErrorCode, null, request); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private ResponseEntity<Object> handleExceptionInternal( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -179,4 +228,40 @@ private ResponseEntity<Object> handleExceptionInternalConstraint( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static String extractRequestUri(WebRequest request) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (request instanceof ServletWebRequest servletWebRequest) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return servletWebRequest.getRequest().getRequestURI(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String desc = request.getDescription(false); // ex: "uri=/api/alarms/100/checkin" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (desc != null && desc.startsWith("uri=")) return desc.substring(4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return desc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static String extractQueryString(WebRequest request) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (request instanceof ServletWebRequest servletWebRequest) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return servletWebRequest.getRequest().getQueryString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static void sendErrorToSentry(Exception ex, String requestUri, String queryString, String errorCode, HttpStatus status) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (status.is5xxServerError()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Sentry.withScope(scope -> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope.setTransaction(requestUri); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope.setTag("path", requestUri); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (errorCode != null && !errorCode.isBlank()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope.setTag("error.code", errorCode); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope.setFingerprint(List.of(errorCode)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (queryString != null && !queryString.isBlank()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope.setExtra("query", queryString); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Sentry.captureException(ex); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Sentry Gradle 플러그인 토큰/프로젝트 설정을 환경별로 유연하게 처리하세요.
로컬/CI 환경에 따라 SENTRY_AUTH_TOKEN이 없을 수 있습니다. 토큰이 없을 때 업로드 관련 태스크가 안전하게 스킵되도록 가드하거나, 프로젝트명을 환경 변수로 파라미터화하면 운영/개발 프로젝트 분리가 쉬워집니다.
아래처럼 조건부로 토큰을 적용하고, 프로젝트명을 환경 변수로 오버라이드 가능하게 하는 것을 제안합니다.
sentry { includeSourceContext = true - org = "akuma-ir" - projectName = "nuntteo_dev" - authToken = System.getenv("SENTRY_AUTH_TOKEN") + org = "akuma-ir" + projectName = System.getenv("SENTRY_PROJECT") ?: "nuntteo_dev" + def token = System.getenv("SENTRY_AUTH_TOKEN") + if (token) { + authToken = token + } }📝 Committable suggestion
🤖 Prompt for AI Agents