From a53fcb09538fa824c6335ec19b0de50707e9dd68 Mon Sep 17 00:00:00 2001 From: mooncw Date: Thu, 21 Nov 2024 17:05:04 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Feat:=20RequestBody=EB=A5=BC=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=97=90=20=EB=82=A8=EA=B8=B0=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20RequestBody=20=EC=BA=90=EC=8B=B1=20=EC=9E=91?= =?UTF-8?q?=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/HttpRequestFilterConfig.java | 19 +++++ .../filter/ReadableRequestWrapper.java | 69 +++++++++++++++++++ .../filter/ReadableRequestWrapperFilter.java | 23 +++++++ 3 files changed, 111 insertions(+) create mode 100644 user-api/src/main/java/com/biengual/userapi/config/HttpRequestFilterConfig.java create mode 100644 user-api/src/main/java/com/biengual/userapi/filter/ReadableRequestWrapper.java create mode 100644 user-api/src/main/java/com/biengual/userapi/filter/ReadableRequestWrapperFilter.java diff --git a/user-api/src/main/java/com/biengual/userapi/config/HttpRequestFilterConfig.java b/user-api/src/main/java/com/biengual/userapi/config/HttpRequestFilterConfig.java new file mode 100644 index 00000000..dda0a8ea --- /dev/null +++ b/user-api/src/main/java/com/biengual/userapi/config/HttpRequestFilterConfig.java @@ -0,0 +1,19 @@ +package com.biengual.userapi.config; + +import com.biengual.userapi.filter.ReadableRequestWrapperFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HttpRequestFilterConfig { + + @Bean + public FilterRegistrationBean readableRequestWrapperFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new ReadableRequestWrapperFilter()); + registrationBean.addUrlPatterns("/*"); + registrationBean.setName("readableRequestWrapperFilter"); + return registrationBean; + } +} diff --git a/user-api/src/main/java/com/biengual/userapi/filter/ReadableRequestWrapper.java b/user-api/src/main/java/com/biengual/userapi/filter/ReadableRequestWrapper.java new file mode 100644 index 00000000..4b4ea95c --- /dev/null +++ b/user-api/src/main/java/com/biengual/userapi/filter/ReadableRequestWrapper.java @@ -0,0 +1,69 @@ +package com.biengual.userapi.filter; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import lombok.AllArgsConstructor; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class ReadableRequestWrapper extends HttpServletRequestWrapper { + + private final Charset encoding; + private byte[] rawData; + private String requestBody; + + public ReadableRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + + String charEncoding = request.getCharacterEncoding(); + this.encoding = StringUtils.isBlank(charEncoding) ? StandardCharsets.UTF_8 : Charset.forName(charEncoding); + + InputStream inputStream = request.getInputStream(); + this.rawData = IOUtils.toByteArray(inputStream); + + this.requestBody = new String(this.rawData, this.encoding); + } + + @Override + public ServletInputStream getInputStream() { + final ByteArrayInputStream buffer = new ByteArrayInputStream(rawData); + return new ServletInputStreamImpl(buffer); + } + + public String getRequestBody() { + return this.requestBody; + } + + @AllArgsConstructor + private static class ServletInputStreamImpl extends ServletInputStream { + private final ByteArrayInputStream buffer; + + @Override + public boolean isFinished() { + return buffer.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener listener) { + + } + + @Override + public int read() { + return buffer.read(); + } + } +} diff --git a/user-api/src/main/java/com/biengual/userapi/filter/ReadableRequestWrapperFilter.java b/user-api/src/main/java/com/biengual/userapi/filter/ReadableRequestWrapperFilter.java new file mode 100644 index 00000000..800d7a1b --- /dev/null +++ b/user-api/src/main/java/com/biengual/userapi/filter/ReadableRequestWrapperFilter.java @@ -0,0 +1,23 @@ +package com.biengual.userapi.filter; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +public class ReadableRequestWrapperFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + try { + ReadableRequestWrapper wrapper = new ReadableRequestWrapper(request); + wrapper.setAttribute("requestBody", wrapper.getRequestBody()); + filterChain.doFilter(wrapper, response); + } catch (Exception e) { + filterChain.doFilter(request, response); + } + } +} From bb4e667c0581ab471cc0cc678a79b6fe70c22bf0 Mon Sep 17 00:00:00 2001 From: mooncw Date: Thu, 21 Nov 2024 17:06:31 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Refactor:=20LoggingAspect=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F?= =?UTF-8?q?=20log=20pattern=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error/GlobalExceptionHandler.java | 3 - .../core/util/GlobalExceptionLogSchema.java | 72 +++++++++++++++++ .../util/RestControllerSuccessLogSchema.java | 76 +++++++++++++++++ .../biengual/userapi/aop/LoggingAspect.java | 81 +++++++++++-------- .../userapi/config/ScheduleConfig.java | 12 ++- user-api/src/main/resources/logback.xml | 5 +- 6 files changed, 211 insertions(+), 38 deletions(-) create mode 100644 biengual-core/src/main/java/com/biengual/core/util/GlobalExceptionLogSchema.java create mode 100644 biengual-core/src/main/java/com/biengual/core/util/RestControllerSuccessLogSchema.java diff --git a/biengual-core/src/main/java/com/biengual/core/response/error/GlobalExceptionHandler.java b/biengual-core/src/main/java/com/biengual/core/response/error/GlobalExceptionHandler.java index a2f51c6a..d0f7b105 100644 --- a/biengual-core/src/main/java/com/biengual/core/response/error/GlobalExceptionHandler.java +++ b/biengual-core/src/main/java/com/biengual/core/response/error/GlobalExceptionHandler.java @@ -27,7 +27,6 @@ public ResponseEntity handleCommonException(CommonException e) { public ResponseEntity handleRequestException(MethodArgumentNotValidException e) { RequestErrorCode errorCode = RequestErrorCode.BAD_REQUEST; errorCode.setMessage(e); - log.error(errorCode.getMessage()); return ResponseEntityFactory.toResponseEntity(errorCode); } @@ -44,14 +43,12 @@ public ResponseEntity RequestParamException(ConstraintViolationException public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { RequestErrorCode errorCode = RequestErrorCode.BAD_REQUEST; errorCode.setMessage(e); - log.error(e.getMessage()); return ResponseEntityFactory.toResponseEntity(errorCode); } // 핸들링하지 않는 Exception 처리 - 실제 Exception 메시지는 로그에만 남도록 함 @ExceptionHandler(Exception.class) public ResponseEntity handleOtherException(Exception e) { - log.error(e.getMessage()); return ResponseEntityFactory.toResponseEntity(SERVER_ERROR); } } diff --git a/biengual-core/src/main/java/com/biengual/core/util/GlobalExceptionLogSchema.java b/biengual-core/src/main/java/com/biengual/core/util/GlobalExceptionLogSchema.java new file mode 100644 index 00000000..c4d6e561 --- /dev/null +++ b/biengual-core/src/main/java/com/biengual/core/util/GlobalExceptionLogSchema.java @@ -0,0 +1,72 @@ +package com.biengual.core.util; + +import com.biengual.core.response.ApiCustomResponse; +import jakarta.servlet.http.HttpServletRequest; +import lombok.Builder; + +@Builder +public record GlobalExceptionLogSchema( + String server, + String ip, + String contentType, + String userAgent, + String user, + String httpMethod, + String uri, + String params, + Object requestBody, + String code, + String message +) { + public static GlobalExceptionLogSchema of( + String server, String user, HttpServletRequest request, ApiCustomResponse response + ) { + Object optionalRequestBody = request.getAttribute("requestBody"); + boolean existsRequestBody = optionalRequestBody.toString().isEmpty(); + + return GlobalExceptionLogSchema.builder() + .server(server) + .ip(request.getRemoteAddr()) + .contentType(request.getContentType()) + .userAgent(request.getHeader("User-Agent")) + .user(user) + .httpMethod(request.getMethod()) + .uri(request.getRequestURI()) + .params(request.getQueryString()) + .requestBody(existsRequestBody ? null : optionalRequestBody) + .code(response.code()) + .message(response.message()) + .build(); + } + + @Override + public String toString() { + return """ + [GLOBAL EXCEPTION] + server: %s, + ip: %s, + contentType: %s, + userAgent: %s, + user: %s, + httpMethod: %s, + uri: %s, + params: %s, + requestBody: %s, + code: %s, + message: %s + """ + .formatted( + this.server, + this.ip, + this.contentType, + this.userAgent, + this.user, + this.httpMethod, + this.uri, + this.params, + this.requestBody, + this.code, + this.message + ); + } +} diff --git a/biengual-core/src/main/java/com/biengual/core/util/RestControllerSuccessLogSchema.java b/biengual-core/src/main/java/com/biengual/core/util/RestControllerSuccessLogSchema.java new file mode 100644 index 00000000..4514df6d --- /dev/null +++ b/biengual-core/src/main/java/com/biengual/core/util/RestControllerSuccessLogSchema.java @@ -0,0 +1,76 @@ +package com.biengual.core.util; + +import com.biengual.core.response.ApiCustomResponse; +import jakarta.servlet.http.HttpServletRequest; +import lombok.Builder; + +@Builder +public record RestControllerSuccessLogSchema( + String server, + String ip, + String contentType, + String userAgent, + String user, + String httpMethod, + String uri, + String params, + Object requestBody, + String code, + String message, + Long responseTime +) { + public static RestControllerSuccessLogSchema of( + String server, String user, Long responseTime, HttpServletRequest request, ApiCustomResponse response + ) { + Object optionalRequestBody = request.getAttribute("requestBody"); + boolean existsRequestBody = optionalRequestBody.toString().isEmpty(); + + return RestControllerSuccessLogSchema.builder() + .server(server) + .ip(request.getRemoteAddr()) + .contentType(request.getContentType()) + .userAgent(request.getHeader("User-Agent")) + .user(user) + .httpMethod(request.getMethod()) + .uri(request.getRequestURI()) + .params(request.getQueryString()) + .requestBody(existsRequestBody ? null : optionalRequestBody) + .code(response.code()) + .message(response.message()) + .responseTime(responseTime) + .build(); + } + + @Override + public String toString() { + return """ + [REST API INFO] + server: %s, + ip: %s, + contentType: %s, + userAgent: %s, + user: %s, + httpMethod: %s, + uri: %s, + params: %s, + requestBody: %s, + code: %s, + message: %s, + responseTime: %sms + """ + .formatted( + this.server, + this.ip, + this.contentType, + this.userAgent, + this.user, + this.httpMethod, + this.uri, + this.params, + this.requestBody, + this.code, + this.message, + this.responseTime + ); + } +} diff --git a/user-api/src/main/java/com/biengual/userapi/aop/LoggingAspect.java b/user-api/src/main/java/com/biengual/userapi/aop/LoggingAspect.java index ebd01892..2ac82185 100644 --- a/user-api/src/main/java/com/biengual/userapi/aop/LoggingAspect.java +++ b/user-api/src/main/java/com/biengual/userapi/aop/LoggingAspect.java @@ -1,12 +1,12 @@ package com.biengual.userapi.aop; import com.biengual.core.response.ApiCustomResponse; +import com.biengual.core.util.GlobalExceptionLogSchema; +import com.biengual.core.util.RestControllerSuccessLogSchema; import com.biengual.userapi.oauth2.info.OAuth2UserPrincipal; - import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.beans.factory.annotation.Value; @@ -14,6 +14,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import static com.biengual.core.response.status.UserServiceStatus.USER_LOGIN_SUCCESS; @@ -37,8 +39,8 @@ public class LoggingAspect { @Pointcut("@within(org.springframework.web.bind.annotation.RestController)") private void restController() {} - @Pointcut("@within(org.springframework.stereotype.Service)") - private void service() {} + @Pointcut("execution(* com.biengual.core.response.error.GlobalExceptionHandler.*(..))") + public void globalException() {} @Pointcut("@annotation(com.biengual.core.annotation.LoginLogging)") private void login() {} @@ -47,25 +49,60 @@ private void login() {} @Around("restController()") public Object logControllerAround(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); - String methodName = joinPoint.getSignature().toShortString(); Object result = joinPoint.proceed(joinPoint.getArgs()); - long executionTime = System.currentTimeMillis() - startTime; + long responseTime = System.currentTimeMillis() - startTime; if (result instanceof ResponseEntity responseEntity) { - Object body = responseEntity.getBody(); + Object responseBody = responseEntity.getBody(); + + if (responseBody instanceof ApiCustomResponse apiResponse) { + HttpServletRequest request = + ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); - if (body instanceof ApiCustomResponse apiResponse) { - String customCode = apiResponse.code(); + String user = this.getUserIdentifier(); - String user = getUserIdentifier(); + RestControllerSuccessLogSchema logSchema = + RestControllerSuccessLogSchema.of(activeProfile, user, responseTime, request, apiResponse); - log.info("server: {}, user: {}, controller: {}, responseTime: {}ms, code: {}", - activeProfile, user, methodName, executionTime, customCode); + log.info(logSchema.toString()); } } return result; } + + // 소셜 로그인 로그를 남기는 메서드 + @After(value = "login() && args(request, response, authentication)", argNames = "request, response, authentication") + public void logLoginAfter(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + OAuth2UserPrincipal principal = (OAuth2UserPrincipal) authentication.getPrincipal(); + String email = principal.getEmail(); + String code = USER_LOGIN_SUCCESS.getServiceStatus(); + + log.info("server: {}, user: {}, code: {}", activeProfile, email, code); + } + + // GlobalException에 대한 로그를 남기는 메서드 + @AfterReturning(pointcut = "globalException()", returning = "result") + public void globalExceptionAfterReturning(Object result) { + if (result instanceof ResponseEntity responseEntity) { + Object responseBody = responseEntity.getBody(); + + if (responseBody instanceof ApiCustomResponse apiResponse) { + HttpServletRequest request = + ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + + String user = this.getUserIdentifier(); + + GlobalExceptionLogSchema logSchema = + GlobalExceptionLogSchema.of(activeProfile, user, request, apiResponse); + + log.error(logSchema.toString()); + } + } + } + + // Internal Method ================================================================================================= + // RestController에 대한 사용자 식별 private String getUserIdentifier() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); @@ -80,24 +117,4 @@ private String getUserIdentifier() { return "guest"; } - - // 소셜 로그인 로그를 남기는 메서드 - @After(value = "login() && args(request, response, authentication)", argNames = "request, response, authentication") - public void logLoginAfter(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - OAuth2UserPrincipal principal = (OAuth2UserPrincipal) authentication.getPrincipal(); - String email = principal.getEmail(); - String code = USER_LOGIN_SUCCESS.getServiceStatus(); - - log.info("server: {}, user: {}, code: {}", activeProfile, email, code); - } - - // RestController 동작에서 발생하는 에러 로그를 남기는 메서드 - @AfterThrowing(pointcut = "restController()", throwing = "e") - public void logException(JoinPoint joinPoint, Exception e) { - String className = joinPoint.getSignature().getDeclaringTypeName(); - String methodName = joinPoint.getSignature().getName(); - String errorMessage = e.getMessage(); - - log.error("server: {}, class: {}, method: {}, message: {}", activeProfile, className, methodName, errorMessage); - } } diff --git a/user-api/src/main/java/com/biengual/userapi/config/ScheduleConfig.java b/user-api/src/main/java/com/biengual/userapi/config/ScheduleConfig.java index 4aa51d05..b0df4e64 100644 --- a/user-api/src/main/java/com/biengual/userapi/config/ScheduleConfig.java +++ b/user-api/src/main/java/com/biengual/userapi/config/ScheduleConfig.java @@ -1,10 +1,20 @@ package com.biengual.userapi.config; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @Configuration @EnableScheduling public class ScheduleConfig { -} + @Bean + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(5); + scheduler.setThreadNamePrefix("scheduled-task-"); + return scheduler; + } +} diff --git a/user-api/src/main/resources/logback.xml b/user-api/src/main/resources/logback.xml index 19f1f304..dacae84a 100644 --- a/user-api/src/main/resources/logback.xml +++ b/user-api/src/main/resources/logback.xml @@ -1,4 +1,5 @@ + @@ -7,8 +8,8 @@ - - + +