11package akuma .whiplash .global .exception ;
22
3+ import akuma .whiplash .global .log .LogUtils ;
34import akuma .whiplash .global .response .ApplicationResponse ;
45import akuma .whiplash .global .response .code .BaseErrorCode ;
56import akuma .whiplash .global .response .code .CommonErrorCode ;
7+ import io .sentry .Sentry ;
68import jakarta .servlet .http .HttpServletRequest ;
79import jakarta .validation .ConstraintViolation ;
810import jakarta .validation .ConstraintViolationException ;
911import java .util .LinkedHashMap ;
12+ import java .util .List ;
1013import java .util .Map ;
1114import java .util .Optional ;
15+ import java .util .stream .Collectors ;
1216import lombok .extern .slf4j .Slf4j ;
1317import org .springframework .http .HttpHeaders ;
1418import org .springframework .http .HttpStatus ;
@@ -45,6 +49,14 @@ public ResponseEntity<Object> handleMethodArgumentNotValid(
4549 (existingErrorMessage , newErrorMessage ) -> existingErrorMessage + ", " + newErrorMessage );
4650 });
4751
52+ sendErrorToSentry (
53+ e ,
54+ extractRequestUri (request ),
55+ LogUtils .maskSensitiveQuery (extractQueryString (request )),
56+ CommonErrorCode .METHOD_ARGUMENT_NOT_VALID .getCustomCode (),
57+ CommonErrorCode .METHOD_ARGUMENT_NOT_VALID .getHttpStatus ()
58+ );
59+
4860 return handleExceptionInternalArgs (
4961 e ,
5062 request ,
@@ -59,6 +71,18 @@ public ResponseEntity<Object> validation(ConstraintViolationException e, WebRequ
5971 .findFirst ()
6072 .orElseThrow (() -> new RuntimeException ("ConstraintViolationException Error" ));
6173
74+ sendErrorToSentry (
75+ e ,
76+ extractRequestUri (request ),
77+ LogUtils .maskSensitiveQuery (
78+ e .getConstraintViolations ().stream ()
79+ .map (v -> v .getPropertyPath () + "=" + v .getInvalidValue ())
80+ .collect (Collectors .joining (", " ))
81+ ),
82+ errorMessage ,
83+ CommonErrorCode .METHOD_ARGUMENT_NOT_VALID .getHttpStatus ()
84+ );
85+
6286 return handleExceptionInternalConstraint (e , CommonErrorCode .valueOf (errorMessage ), request );
6387 }
6488
@@ -74,13 +98,30 @@ protected ResponseEntity<Object> handleHttpMessageNotReadable(
7498 CommonErrorCode .METHOD_ARGUMENT_NOT_VALID .getCustomCode (),
7599 CommonErrorCode .METHOD_ARGUMENT_NOT_VALID .getMessage ()
76100 );
101+
102+ sendErrorToSentry (
103+ ex ,
104+ extractRequestUri (request ),
105+ LogUtils .maskSensitiveQuery (extractQueryString (request )),
106+ CommonErrorCode .METHOD_ARGUMENT_NOT_VALID .getCustomCode (),
107+ CommonErrorCode .METHOD_ARGUMENT_NOT_VALID .getHttpStatus ()
108+ );
109+
77110 return new ResponseEntity <>(response , HttpStatus .BAD_REQUEST );
78111 }
79112
80113 @ ExceptionHandler
81114 public ResponseEntity <Object > exception (Exception e , WebRequest request ) {
82115 log .error ("Unexpected error: " , e );
83116
117+ sendErrorToSentry (
118+ e ,
119+ request .getDescription (false ),
120+ request .getParameterMap ().toString (),
121+ CommonErrorCode .INTERNAL_SERVER_ERROR .getCustomCode (),
122+ CommonErrorCode .INTERNAL_SERVER_ERROR .getHttpStatus ()
123+ );
124+
84125 return handleExceptionInternalFalse (
85126 e ,
86127 CommonErrorCode .INTERNAL_SERVER_ERROR .getHttpStatus (),
@@ -90,10 +131,18 @@ public ResponseEntity<Object> exception(Exception e, WebRequest request) {
90131 }
91132
92133 @ ExceptionHandler (value = ApplicationException .class )
93- public ResponseEntity <Object > onThrowException (ApplicationException applicationException , HttpServletRequest request ) {
94- BaseErrorCode baseErrorCode = applicationException .getCode ();
134+ public ResponseEntity <Object > onThrowException (ApplicationException ex , HttpServletRequest request ) {
135+ BaseErrorCode baseErrorCode = ex .getCode ();
95136
96- return handleExceptionInternal (applicationException , baseErrorCode , null , request );
137+ sendErrorToSentry (
138+ ex ,
139+ request .getRequestURI (),
140+ LogUtils .maskSensitiveQuery (request .getQueryString ()),
141+ baseErrorCode .getCustomCode (),
142+ baseErrorCode .getHttpStatus ()
143+ );
144+
145+ return handleExceptionInternal (ex , baseErrorCode , null , request );
97146 }
98147
99148 private ResponseEntity <Object > handleExceptionInternal (
@@ -179,4 +228,40 @@ private ResponseEntity<Object> handleExceptionInternalConstraint(
179228 request
180229 );
181230 }
231+
232+ private static String extractRequestUri (WebRequest request ) {
233+ if (request instanceof ServletWebRequest servletWebRequest ) {
234+ return servletWebRequest .getRequest ().getRequestURI ();
235+ }
236+ String desc = request .getDescription (false ); // ex: "uri=/api/alarms/100/checkin"
237+ if (desc != null && desc .startsWith ("uri=" )) return desc .substring (4 );
238+ return desc ;
239+ }
240+
241+ private static String extractQueryString (WebRequest request ) {
242+ if (request instanceof ServletWebRequest servletWebRequest ) {
243+ return servletWebRequest .getRequest ().getQueryString ();
244+ }
245+ return null ;
246+ }
247+
248+ private static void sendErrorToSentry (Exception ex , String requestUri , String queryString , String errorCode , HttpStatus status ) {
249+ if (status .is5xxServerError ()) {
250+ Sentry .withScope (scope -> {
251+ scope .setTransaction (requestUri );
252+ scope .setTag ("path" , requestUri );
253+
254+ if (errorCode != null && !errorCode .isBlank ()) {
255+ scope .setTag ("error.code" , errorCode );
256+ scope .setFingerprint (List .of (errorCode ));
257+ }
258+
259+ if (queryString != null && !queryString .isBlank ()) {
260+ scope .setExtra ("query" , queryString );
261+ }
262+
263+ Sentry .captureException (ex );
264+ });
265+ }
266+ }
182267}
0 commit comments