복붙노트

[SPRING] Spring Boot - 한 곳에서 모든 요청과 응답을 예외로 기록하는 방법?

SPRING

Spring Boot - 한 곳에서 모든 요청과 응답을 예외로 기록하는 방법?

나는 스프링 부트로 나머지 API 작업을하고있다. 입력 매개 변수 (예 : GET, POST 등)를 사용하여 요청을 모두 기록하고, 요청 경로, 쿼리 문자열,이 요청의 해당 클래스 메소드,이 작업의 응답 (성공과 오류)을 모두 기록해야합니다.

예를 들면 다음과 같습니다.

성공적인 요청 :

http://example.com/api/users/1

로그는 다음과 같이 보입니다.

{
   HttpStatus: 200,
   path: "api/users/1",
   method: "GET",
   clientIp: "0.0.0.0",
   accessToken: "XHGu6as5dajshdgau6i6asdjhgjhg",
   method: "UsersController.getUser",
   arguments: {
     id: 1 
   },
   response: {
      user: {
        id: 1,
        username: "user123",
        email: "user123@example.com"   
      }
   },
   exceptions: []       
}

또는 오류가있는 요청 :

http://example.com/api/users/9999

로그는 다음과 같이되어야합니다.

    {
       HttpStatus: 404,
       errorCode: 101,                 
       path: "api/users/9999",
       method: "GET",
       clientIp: "0.0.0.0",
       accessToken: "XHGu6as5dajshdgau6i6asdjhgjhg",
       method: "UsersController.getUser",
       arguments: {
         id: 9999 
       },
       returns: {            
       },
       exceptions: [
         {
           exception: "UserNotFoundException",
           message: "User with id 9999 not found",
           exceptionId: "adhaskldjaso98d7324kjh989",
           stacktrace: ...................    
       ]       
    }

요청 / 응답이 성공적인 엔터티 및 오류 케이스에서이 엔터티와 관련된 사용자 지정 정보와 함께 단일 엔터티가되기를 원합니다.

이것을 달성하기 위해 봄에 가장 좋은 방법은 필터를 사용하는 것일 수 있습니까? 그렇다면 구체적인 예를 들어 줄 수 있습니까?

(나는 @ControllerAdvice와 @ExceptionHandler를 가지고 놀았지만, 앞에서 언급했듯이 모든 성공과 오류 요청을 단일 장소 (그리고 단일 로그)에서 처리해야한다.

해결법

  1. ==============================

    1.인터셉터, 필터, 컴포넌트, 애스펙트 등을 작성하지 마십시오. 이것은 매우 일반적인 문제이며 여러 번 해결되었습니다. Spring Boot는 박스로부터 HTTP 요청 로그를 제공하는 Actuator라는 모듈을 가지고 있습니다. 마지막 100 개의 HTTP 요청을 표시 할 / trace (SB1.x) 또는 / 액추에이터 / httptrace (SB2.0 +)에 엔드 포인트가 매핑됩니다. 각 요청을 기록하거나 DB에 쓸 수 있도록 사용자 정의 할 수 있습니다. 원하는 엔드 포인트를 얻으려면 액추에이터 springboot 종속성이 필요하며 찾고있는 엔드 포인트를 "허용"하고 보안을 설정 또는 해제 할 수 있습니다.

    인터셉터, 필터, 컴포넌트, 애스펙트 등을 작성하지 마십시오. 이것은 매우 일반적인 문제이며 여러 번 해결되었습니다. Spring Boot는 박스로부터 HTTP 요청 로그를 제공하는 Actuator라는 모듈을 가지고 있습니다. 마지막 100 개의 HTTP 요청을 표시 할 / trace (SB1.x) 또는 / 액추에이터 / httptrace (SB2.0 +)에 엔드 포인트가 매핑됩니다. 각 요청을 기록하거나 DB에 쓸 수 있도록 사용자 정의 할 수 있습니다. 원하는 엔드 포인트를 얻으려면 액추에이터 springboot 종속성이 필요하며 찾고있는 엔드 포인트를 "허용"하고 보안을 설정 또는 해제 할 수 있습니다.

    또한이 응용 프로그램은 어디서 실행됩니까? PaaS를 사용합니까? 예를 들어, Heroku 호스팅 제공 업체는 서비스의 일부로 요청 로깅을 제공하므로 사용자는 코딩을 전혀 할 필요가 없습니다.

  2. ==============================

    2.Spring은 이미이 작업을 수행하는 필터를 제공합니다. 다음 Bean을 설정에 추가하십시오.

    Spring은 이미이 작업을 수행하는 필터를 제공합니다. 다음 Bean을 설정에 추가하십시오.

    @Bean
    public CommonsRequestLoggingFilter requestLoggingFilter() {
        CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
        loggingFilter.setIncludeClientInfo(true);
        loggingFilter.setIncludeQueryString(true);
        loggingFilter.setIncludePayload(true);
        return loggingFilter;
    }
    

    org.springframework.web.filter.CommonsRequestLoggingFilter의 로그 레벨을 DEBUG로 변경하는 것을 잊지 마십시오.

  3. ==============================

    3.실행 된 java 메소드를 로깅 할 필요가 없으면 javax.servlet.Filter를 사용할 수 있습니다.

    실행 된 java 메소드를 로깅 할 필요가 없으면 javax.servlet.Filter를 사용할 수 있습니다.

    그러나이 요구 사항을 사용하면 DispatcherServlet의 handlerMapping에 저장된 정보에 액세스해야합니다. 즉, 요청 / 응답 쌍의 로깅을 수행하기 위해 DispatcherServlet을 대체 할 수 있습니다.

    아래는 귀하의 요구에 더욱 부각되고 채택 될 수있는 아이디어의 예입니다.

    public class LoggableDispatcherServlet extends DispatcherServlet {
    
        private final Log logger = LogFactory.getLog(getClass());
    
        @Override
        protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            if (!(request instanceof ContentCachingRequestWrapper)) {
                request = new ContentCachingRequestWrapper(request);
            }
            if (!(response instanceof ContentCachingResponseWrapper)) {
                response = new ContentCachingResponseWrapper(response);
            }
            HandlerExecutionChain handler = getHandler(request);
    
            try {
                super.doDispatch(request, response);
            } finally {
                log(request, response, handler);
                updateResponse(response);
            }
        }
    
        private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, HandlerExecutionChain handler) {
            LogMessage log = new LogMessage();
            log.setHttpStatus(responseToCache.getStatus());
            log.setHttpMethod(requestToCache.getMethod());
            log.setPath(requestToCache.getRequestURI());
            log.setClientIp(requestToCache.getRemoteAddr());
            log.setJavaMethod(handler.toString());
            log.setResponse(getResponsePayload(responseToCache));
            logger.info(log);
        }
    
        private String getResponsePayload(HttpServletResponse response) {
            ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
            if (wrapper != null) {
    
                byte[] buf = wrapper.getContentAsByteArray();
                if (buf.length > 0) {
                    int length = Math.min(buf.length, 5120);
                    try {
                        return new String(buf, 0, length, wrapper.getCharacterEncoding());
                    }
                    catch (UnsupportedEncodingException ex) {
                        // NOOP
                    }
                }
            }
            return "[unknown]";
        }
    
        private void updateResponse(HttpServletResponse response) throws IOException {
            ContentCachingResponseWrapper responseWrapper =
                WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
            responseWrapper.copyBodyToResponse();
        }
    
    }
    

    HandlerExecutionChain - 요청 처리기에 대한 정보가 들어 있습니다.

    그런 다음이 디스패처를 다음과 같이 등록 할 수 있습니다.

        @Bean
        public ServletRegistrationBean dispatcherRegistration() {
            return new ServletRegistrationBean(dispatcherServlet());
        }
    
        @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet() {
            return new LoggableDispatcherServlet();
        }
    

    다음은 로그의 샘플입니다.

    http http://localhost:8090/settings/test
    i.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=500, path='/error', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] and 3 interceptors', arguments=null, response='{"timestamp":1472475814077,"status":500,"error":"Internal Server Error","exception":"java.lang.RuntimeException","message":"org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.RuntimeException","path":"/settings/test"}'}
    
    http http://localhost:8090/settings/params
    i.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=200, path='/settings/httpParams', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public x.y.z.DTO x.y.z.Controller.params()] and 3 interceptors', arguments=null, response='{}'}
    
    http http://localhost:8090/123
    i.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=404, path='/error', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] and 3 interceptors', arguments=null, response='{"timestamp":1472475840592,"status":404,"error":"Not Found","message":"Not Found","path":"/123"}'}
    

    최신 정보

    오류가 발생하면 Spring은 자동 오류 처리를 수행합니다. 따라서 BasicErrorController # 오류는 요청 처리기로 표시됩니다. 원래 요청 처리기를 유지하려면 #processDispatchResult가 호출되기 전에 spring-webmvc-4.2.5.RELEASE-sources.jar! /org/springframework/web/servlet/DispatcherServlet.java : 971에서이 동작을 무시할 수 있습니다. 원래 처리기를 캐시합니다.

  4. ==============================

    4.로그 북 라이브러리는 특히 HTTP 요청 및 응답을 로깅하기 위해 만들어집니다. 특별한 스타터 라이브러리를 사용하여 스프링 부트를 지원합니다.

    로그 북 라이브러리는 특히 HTTP 요청 및 응답을 로깅하기 위해 만들어집니다. 특별한 스타터 라이브러리를 사용하여 스프링 부트를 지원합니다.

    Spring Boot에서 로깅을 가능하게하려면 프로젝트 의존성에 라이브러리를 추가하기 만하면된다. 예를 들어 Maven을 사용한다고 가정합니다.

    <dependency>
        <groupId>org.zalando</groupId>
        <artifactId>logbook-spring-boot-starter</artifactId>
        <version>1.5.0</version>
    </dependency>
    

    기본적으로 로깅 결과는 다음과 같습니다.

    {
      "origin" : "local",
      "correlation" : "52e19498-890c-4f75-a06c-06ddcf20836e",
      "status" : 200,
      "headers" : {
        "X-Application-Context" : [
          "application:8088"
        ],
        "Content-Type" : [
          "application/json;charset=UTF-8"
        ],
        "Transfer-Encoding" : [
          "chunked"
        ],
        "Date" : [
          "Sun, 24 Dec 2017 13:10:45 GMT"
        ]
      },
      "body" : {
        "thekey" : "some_example"
      },
      "duration" : 105,
      "protocol" : "HTTP/1.1",
      "type" : "response"
    }
    

    그러나 요청을 처리하는 클래스 이름은 출력하지 않습니다. 라이브러리에는 사용자 정의 로거 작성을위한 인터페이스가 있습니다.

  5. ==============================

    5.여기 내가 봄 데이터 휴식에서 어떻게하는지입니다. 사용하여 org.springframework.web.util.ContentCachingRequestWrapper 및 org.springframework.web.util.ContentCachingResponseWrapper

    여기 내가 봄 데이터 휴식에서 어떻게하는지입니다. 사용하여 org.springframework.web.util.ContentCachingRequestWrapper 및 org.springframework.web.util.ContentCachingResponseWrapper

    /**
     * Doogies very cool HTTP request logging
     *
     * There is also {@link org.springframework.web.filter.CommonsRequestLoggingFilter}  but it cannot log request method
     * And it cannot easily be extended.
     *
     * https://mdeinum.wordpress.com/2015/07/01/spring-framework-hidden-gems/
     * http://stackoverflow.com/questions/8933054/how-to-read-and-copy-the-http-servlet-response-output-stream-content-for-logging
     */
    public class DoogiesRequestLogger extends OncePerRequestFilter {
    
      private boolean includeResponsePayload = true;
      private int maxPayloadLength = 1000;
    
      private String getContentAsString(byte[] buf, int maxLength, String charsetName) {
        if (buf == null || buf.length == 0) return "";
        int length = Math.min(buf.length, this.maxPayloadLength);
        try {
          return new String(buf, 0, length, charsetName);
        } catch (UnsupportedEncodingException ex) {
          return "Unsupported Encoding";
        }
      }
    
      /**
       * Log each request and respponse with full Request URI, content payload and duration of the request in ms.
       * @param request the request
       * @param response the response
       * @param filterChain chain of filters
       * @throws ServletException
       * @throws IOException
       */
      @Override
      protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
        long startTime = System.currentTimeMillis();
        StringBuffer reqInfo = new StringBuffer()
         .append("[")
         .append(startTime % 10000)  // request ID
         .append("] ")
         .append(request.getMethod())
         .append(" ")
         .append(request.getRequestURL());
    
        String queryString = request.getQueryString();
        if (queryString != null) {
          reqInfo.append("?").append(queryString);
        }
    
        if (request.getAuthType() != null) {
          reqInfo.append(", authType=")
            .append(request.getAuthType());
        }
        if (request.getUserPrincipal() != null) {
          reqInfo.append(", principalName=")
            .append(request.getUserPrincipal().getName());
        }
    
        this.logger.debug("=> " + reqInfo);
    
        // ========= Log request and response payload ("body") ========
        // We CANNOT simply read the request payload here, because then the InputStream would be consumed and cannot be read again by the actual processing/server.
        //    String reqBody = DoogiesUtil._stream2String(request.getInputStream());   // THIS WOULD NOT WORK!
        // So we need to apply some stronger magic here :-)
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
    
        filterChain.doFilter(wrappedRequest, wrappedResponse);     // ======== This performs the actual request!
        long duration = System.currentTimeMillis() - startTime;
    
        // I can only log the request's body AFTER the request has been made and ContentCachingRequestWrapper did its work.
        String requestBody = this.getContentAsString(wrappedRequest.getContentAsByteArray(), this.maxPayloadLength, request.getCharacterEncoding());
        if (requestBody.length() > 0) {
          this.logger.debug("   Request body:\n" +requestBody);
        }
    
        this.logger.debug("<= " + reqInfo + ": returned status=" + response.getStatus() + " in "+duration + "ms");
        if (includeResponsePayload) {
          byte[] buf = wrappedResponse.getContentAsByteArray();
          this.logger.debug("   Response body:\n"+getContentAsString(buf, this.maxPayloadLength, response.getCharacterEncoding()));
        }
    
        wrappedResponse.copyBodyToResponse();  // IMPORTANT: copy content of response back into original response
    
      }
    
    
    }
    
  6. ==============================

    6.당신이 Spring AOP를 시도하는 것을 꺼린다면, 이것은 로깅 목적을 위해 탐구 해 왔으며 꽤 잘 작동한다. 그것은 정의되지 않은 로그 요청과 요청 시도에 실패했습니다.

    당신이 Spring AOP를 시도하는 것을 꺼린다면, 이것은 로깅 목적을 위해 탐구 해 왔으며 꽤 잘 작동한다. 그것은 정의되지 않은 로그 요청과 요청 시도에 실패했습니다.

    이 세 가지 종속성을 추가하십시오.

    spring-aop, aspectjrt, aspectjweaver
    

    이것을 XML 설정 파일 에 추가하십시오.

    포인트 컷으로 사용할 수있는 주석을 만듭니다.

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD,ElementType.TYPE})
    public @interface EnableLogging {
    ActionType actionType();
    }
    

    이제 로그 할 모든 나머지 API 메소드에 주석을 달아주세요.

    @EnableLogging(actionType = ActionType.SOME_EMPLOYEE_ACTION)
    @Override
    public Response getEmployees(RequestDto req, final String param) {
    ...
    }
    

    이제 Aspect로갑니다. component-이 클래스가있는 패키지를 검사합니다.

    @Aspect
    @Component
    public class Aspects {
    
    @AfterReturning(pointcut = "execution(@co.xyz.aspect.EnableLogging * *(..)) && @annotation(enableLogging) && args(reqArg, reqArg1,..)", returning = "result")
    public void auditInfo(JoinPoint joinPoint, Object result, EnableLogging enableLogging, Object reqArg, String reqArg1) {
    
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                .getRequest();
    
        if (result instanceof Response) {
            Response responseObj = (Response) result;
    
        String requestUrl = request.getScheme() + "://" + request.getServerName()
                    + ":" + request.getServerPort() + request.getContextPath() + request.getRequestURI()
                    + "?" + request.getQueryString();
    
    String clientIp = request.getRemoteAddr();
    String clientRequest = reqArg.toString();
    int httpResponseStatus = responseObj.getStatus();
    responseObj.getEntity();
    // Can log whatever stuff from here in a single spot.
    }
    
    
    @AfterThrowing(pointcut = "execution(@co.xyz.aspect.EnableLogging * *(..)) && @annotation(enableLogging) && args(reqArg, reqArg1,..)", throwing="exception")
    public void auditExceptionInfo(JoinPoint joinPoint, Throwable exception, EnableLogging enableLogging, Object reqArg, String reqArg1) {
    
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                .getRequest();
    
        String requestUrl = request.getScheme() + "://" + request.getServerName()
        + ":" + request.getServerPort() + request.getContextPath() + request.getRequestURI()
        + "?" + request.getQueryString();
    
        exception.getMessage();
        exception.getCause();
        exception.printStackTrace();
        exception.getLocalizedMessage();
        // Can log whatever exceptions, requests, etc from here in a single spot.
        }
    }
    

    자세한 내용을 읽으려면이 부분을 읽으십시오. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html

  7. ==============================

    7.스프링 부트 bassed 어플리케이션에 엑츄에이터를 추가 한 후에 최신 요청 정보로 / endpoint를 사용할 수 있습니다. 이 엔드 포인트는 TraceRepository를 기반으로 작동하며 기본 구현은 마지막 100 개의 호출을 저장하는 InMemoryTraceRepository입니다. 이 인터페이스를 직접 구현하여 변경하고 Spring 빈으로 사용할 수있게 만들 수 있습니다. 예를 들어, 모든 요청을 로그에 기록하려면 (그리고 여전히 추적 구현에 대한 정보를 제공하기위한 기본 저장소로 기본 구현을 사용하십시오) 다음과 같은 구현을 사용합니다.

    스프링 부트 bassed 어플리케이션에 엑츄에이터를 추가 한 후에 최신 요청 정보로 / endpoint를 사용할 수 있습니다. 이 엔드 포인트는 TraceRepository를 기반으로 작동하며 기본 구현은 마지막 100 개의 호출을 저장하는 InMemoryTraceRepository입니다. 이 인터페이스를 직접 구현하여 변경하고 Spring 빈으로 사용할 수있게 만들 수 있습니다. 예를 들어, 모든 요청을 로그에 기록하려면 (그리고 여전히 추적 구현에 대한 정보를 제공하기위한 기본 저장소로 기본 구현을 사용하십시오) 다음과 같은 구현을 사용합니다.

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
    import org.springframework.boot.actuate.trace.Trace;
    import org.springframework.boot.actuate.trace.TraceRepository;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    import java.util.Map;
    
    
    @Component
    public class LoggingTraceRepository implements TraceRepository {
    
      private static final Logger LOG = LoggerFactory.getLogger(LoggingTraceRepository.class);
      private final TraceRepository delegate = new InMemoryTraceRepository();
    
      @Override
      public List<Trace> findAll() {
        return delegate.findAll();
      }
    
      @Override
      public void add(Map<String, Object> traceInfo) {
        LOG.info(traceInfo.toString());
        this.delegate.add(traceInfo);
      }
    }
    

    이 traceInfo 맵은 이러한 종류의 양식에서 요청 및 응답에 대한 기본 정보를 포함합니다. {method = GET, path = / api / hello / John, headers = {request = {호스트 = localhost : 8080, user-agent = curl / 7.51.0, accept = * / *}, response = {X-Application- 컨텍스트 = 응용 프로그램, Content-Type = text / plain; charset = UTF-8, Content-Length = 10, Date = Wed, 29 Mar 2017 20:41:21 GMT, status = 200}}}. 여기에 응답 내용이 없습니다.

    편집하다! POST 데이터 로깅

    WebRequestTraceFilter를 재정 의하여 POST 데이터에 액세스 할 수 있지만 좋은 생각은 아닙니다 (예 : 업로드 된 모든 파일 내용이 로그로 이동) 다음은 샘플 코드이지만 사용하지 마십시오.

    package info.fingo.nuntius.acuate.trace;
    
    import org.apache.commons.io.IOUtils;
    import org.springframework.boot.actuate.trace.TraceProperties;
    import org.springframework.boot.actuate.trace.TraceRepository;
    import org.springframework.boot.actuate.trace.WebRequestTraceFilter;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.nio.charset.Charset;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    @Component
    public class CustomWebTraceFilter extends WebRequestTraceFilter {
    
      public CustomWebTraceFilter(TraceRepository repository, TraceProperties properties) {
        super(repository, properties);
    }
    
      @Override
      protected Map<String, Object> getTrace(HttpServletRequest request) {
        Map<String, Object> trace = super.getTrace(request);
        String multipartHeader = request.getHeader("content-type");
        if (multipartHeader != null && multipartHeader.startsWith("multipart/form-data")) {
            Map<String, Object> parts = new LinkedHashMap<>();
            try {
                request.getParts().forEach(
                        part -> {
                            try {
                                parts.put(part.getName(), IOUtils.toString(part.getInputStream(), Charset.forName("UTF-8")));
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                );
            } catch (IOException | ServletException e) {
                e.printStackTrace();
            }
            if (!parts.isEmpty()) {
                trace.put("multipart-content-map", parts);
            }
        }
        return trace;
      }
    }
    
  8. ==============================

    8.이 코드는 스프링 부트 응용 프로그램에서 작동합니다. 필터로 등록하십시오.

    이 코드는 스프링 부트 응용 프로그램에서 작동합니다. 필터로 등록하십시오.

        import java.io.BufferedReader;
        import java.io.ByteArrayInputStream;
        import java.io.ByteArrayOutputStream;
        import java.io.IOException;
        import java.io.InputStream;
        import java.io.InputStreamReader;
        import java.io.OutputStream;
        import java.io.PrintWriter;
        import java.util.Collection;
        import java.util.Enumeration;
        import java.util.HashMap;
        import java.util.Locale;
        import java.util.Map;
        import javax.servlet.*;
        import javax.servlet.http.Cookie;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletRequestWrapper;
        import javax.servlet.http.HttpServletResponse;
        import org.apache.commons.io.output.TeeOutputStream;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        import org.springframework.stereotype.Component;
    
        @Component
        public class HttpLoggingFilter implements Filter {
    
            private static final Logger log = LoggerFactory.getLogger(HttpLoggingFilter.class);
    
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }
    
            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                                 FilterChain chain) throws IOException, ServletException {
                try {
                    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    
                    Map<String, String> requestMap = this
                            .getTypesafeRequestMap(httpServletRequest);
                    BufferedRequestWrapper bufferedRequest = new BufferedRequestWrapper(
                            httpServletRequest);
                    BufferedResponseWrapper bufferedResponse = new BufferedResponseWrapper(
                            httpServletResponse);
    
                    final StringBuilder logMessage = new StringBuilder(
                            "REST Request - ").append("[HTTP METHOD:")
                            .append(httpServletRequest.getMethod())
                            .append("] [PATH INFO:")
                            .append(httpServletRequest.getServletPath())
                            .append("] [REQUEST PARAMETERS:").append(requestMap)
                            .append("] [REQUEST BODY:")
                            .append(bufferedRequest.getRequestBody())
                            .append("] [REMOTE ADDRESS:")
                            .append(httpServletRequest.getRemoteAddr()).append("]");
    
                    chain.doFilter(bufferedRequest, bufferedResponse);
                    logMessage.append(" [RESPONSE:")
                            .append(bufferedResponse.getContent()).append("]");
                    log.debug(logMessage.toString());
                } catch (Throwable a) {
                    log.error(a.getMessage());
                }
            }
    
            private Map<String, String> getTypesafeRequestMap(HttpServletRequest request) {
                Map<String, String> typesafeRequestMap = new HashMap<String, String>();
                Enumeration<?> requestParamNames = request.getParameterNames();
                while (requestParamNames.hasMoreElements()) {
                    String requestParamName = (String) requestParamNames.nextElement();
                    String requestParamValue;
                    if (requestParamName.equalsIgnoreCase("password")) {
                        requestParamValue = "********";
                    } else {
                        requestParamValue = request.getParameter(requestParamName);
                    }
                    typesafeRequestMap.put(requestParamName, requestParamValue);
                }
                return typesafeRequestMap;
            }
    
            @Override
            public void destroy() {
            }
    
            private static final class BufferedRequestWrapper extends
                    HttpServletRequestWrapper {
    
                private ByteArrayInputStream bais = null;
                private ByteArrayOutputStream baos = null;
                private BufferedServletInputStream bsis = null;
                private byte[] buffer = null;
    
                public BufferedRequestWrapper(HttpServletRequest req)
                        throws IOException {
                    super(req);
                    // Read InputStream and store its content in a buffer.
                    InputStream is = req.getInputStream();
                    this.baos = new ByteArrayOutputStream();
                    byte buf[] = new byte[1024];
                    int read;
                    while ((read = is.read(buf)) > 0) {
                        this.baos.write(buf, 0, read);
                    }
                    this.buffer = this.baos.toByteArray();
                }
    
                @Override
                public ServletInputStream getInputStream() {
                    this.bais = new ByteArrayInputStream(this.buffer);
                    this.bsis = new BufferedServletInputStream(this.bais);
                    return this.bsis;
                }
    
                String getRequestBody() throws IOException {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(
                            this.getInputStream()));
                    String line = null;
                    StringBuilder inputBuffer = new StringBuilder();
                    do {
                        line = reader.readLine();
                        if (null != line) {
                            inputBuffer.append(line.trim());
                        }
                    } while (line != null);
                    reader.close();
                    return inputBuffer.toString().trim();
                }
    
            }
    
            private static final class BufferedServletInputStream extends
                    ServletInputStream {
    
                private ByteArrayInputStream bais;
    
                public BufferedServletInputStream(ByteArrayInputStream bais) {
                    this.bais = bais;
                }
    
                @Override
                public int available() {
                    return this.bais.available();
                }
    
                @Override
                public int read() {
                    return this.bais.read();
                }
    
                @Override
                public int read(byte[] buf, int off, int len) {
                    return this.bais.read(buf, off, len);
                }
    
                @Override
                public boolean isFinished() {
                    return false;
                }
    
                @Override
                public boolean isReady() {
                    return true;
                }
    
                @Override
                public void setReadListener(ReadListener readListener) {
    
                }
            }
    
            public class TeeServletOutputStream extends ServletOutputStream {
    
                private final TeeOutputStream targetStream;
    
                public TeeServletOutputStream(OutputStream one, OutputStream two) {
                    targetStream = new TeeOutputStream(one, two);
                }
    
                @Override
                public void write(int arg0) throws IOException {
                    this.targetStream.write(arg0);
                }
    
                public void flush() throws IOException {
                    super.flush();
                    this.targetStream.flush();
                }
    
                public void close() throws IOException {
                    super.close();
                    this.targetStream.close();
                }
    
                @Override
                public boolean isReady() {
                    return false;
                }
    
                @Override
                public void setWriteListener(WriteListener writeListener) {
    
                }
            }
    
            public class BufferedResponseWrapper implements HttpServletResponse {
    
                HttpServletResponse original;
                TeeServletOutputStream tee;
                ByteArrayOutputStream bos;
    
                public BufferedResponseWrapper(HttpServletResponse response) {
                    original = response;
                }
    
                public String getContent() {
                    return bos.toString();
                }
    
                public PrintWriter getWriter() throws IOException {
                    return original.getWriter();
                }
    
                public ServletOutputStream getOutputStream() throws IOException {
                    if (tee == null) {
                        bos = new ByteArrayOutputStream();
                        tee = new TeeServletOutputStream(original.getOutputStream(),
                                bos);
                    }
                    return tee;
    
                }
    
                @Override
                public String getCharacterEncoding() {
                    return original.getCharacterEncoding();
                }
    
                @Override
                public String getContentType() {
                    return original.getContentType();
                }
    
                @Override
                public void setCharacterEncoding(String charset) {
                    original.setCharacterEncoding(charset);
                }
    
                @Override
                public void setContentLength(int len) {
                    original.setContentLength(len);
                }
    
                @Override
                public void setContentLengthLong(long l) {
                    original.setContentLengthLong(l);
                }
    
                @Override
                public void setContentType(String type) {
                    original.setContentType(type);
                }
    
                @Override
                public void setBufferSize(int size) {
                    original.setBufferSize(size);
                }
    
                @Override
                public int getBufferSize() {
                    return original.getBufferSize();
                }
    
                @Override
                public void flushBuffer() throws IOException {
                    tee.flush();
                }
    
                @Override
                public void resetBuffer() {
                    original.resetBuffer();
                }
    
                @Override
                public boolean isCommitted() {
                    return original.isCommitted();
                }
    
                @Override
                public void reset() {
                    original.reset();
                }
    
                @Override
                public void setLocale(Locale loc) {
                    original.setLocale(loc);
                }
    
                @Override
                public Locale getLocale() {
                    return original.getLocale();
                }
    
                @Override
                public void addCookie(Cookie cookie) {
                    original.addCookie(cookie);
                }
    
                @Override
                public boolean containsHeader(String name) {
                    return original.containsHeader(name);
                }
    
                @Override
                public String encodeURL(String url) {
                    return original.encodeURL(url);
                }
    
                @Override
                public String encodeRedirectURL(String url) {
                    return original.encodeRedirectURL(url);
                }
    
                @SuppressWarnings("deprecation")
                @Override
                public String encodeUrl(String url) {
                    return original.encodeUrl(url);
                }
    
                @SuppressWarnings("deprecation")
                @Override
                public String encodeRedirectUrl(String url) {
                    return original.encodeRedirectUrl(url);
                }
    
                @Override
                public void sendError(int sc, String msg) throws IOException {
                    original.sendError(sc, msg);
                }
    
                @Override
                public void sendError(int sc) throws IOException {
                    original.sendError(sc);
                }
    
                @Override
                public void sendRedirect(String location) throws IOException {
                    original.sendRedirect(location);
                }
    
                @Override
                public void setDateHeader(String name, long date) {
                    original.setDateHeader(name, date);
                }
    
                @Override
                public void addDateHeader(String name, long date) {
                    original.addDateHeader(name, date);
                }
    
                @Override
                public void setHeader(String name, String value) {
                    original.setHeader(name, value);
                }
    
                @Override
                public void addHeader(String name, String value) {
                    original.addHeader(name, value);
                }
    
                @Override
                public void setIntHeader(String name, int value) {
                    original.setIntHeader(name, value);
                }
    
                @Override
                public void addIntHeader(String name, int value) {
                    original.addIntHeader(name, value);
                }
    
                @Override
                public void setStatus(int sc) {
                    original.setStatus(sc);
                }
    
                @SuppressWarnings("deprecation")
                @Override
                public void setStatus(int sc, String sm) {
                    original.setStatus(sc, sm);
                }
    
                @Override
                public String getHeader(String arg0) {
                    return original.getHeader(arg0);
                }
    
                @Override
                public Collection<String> getHeaderNames() {
                    return original.getHeaderNames();
                }
    
                @Override
                public Collection<String> getHeaders(String arg0) {
                    return original.getHeaders(arg0);
                }
    
                @Override
                public int getStatus() {
                    return original.getStatus();
                }
    
            }
        }
    
  9. ==============================

    9.내가 요청 / 응답을 인쇄 application.properties에서 로깅 수준을 정의했다, 로그 파일에 메서드 URL을

    내가 요청 / 응답을 인쇄 application.properties에서 로깅 수준을 정의했다, 로그 파일에 메서드 URL을

    logging.level.org.springframework.web=DEBUG
    logging.level.org.hibernate.SQL=INFO
    logging.file=D:/log/myapp.log
    

    나는 Spring Boot를 사용했다.

  10. ==============================

    10.@ hahn의 대답은 나를 위해 작동하기 위해 약간의 수정이 필요했지만 지금까지 얻을 수있는 가장 맞춤 설정할 수있는 것입니다.

    @ hahn의 대답은 나를 위해 작동하기 위해 약간의 수정이 필요했지만 지금까지 얻을 수있는 가장 맞춤 설정할 수있는 것입니다.

    HandlerInterceptorAdapter [??]가 있기 때문에 아마 저 버전에서 서버로부터 나쁜 응답을 얻었 기 때문에 저에게 효과가 없었을 것입니다. 여기 그것에 대한 나의 수정이있다.

    public class LoggableDispatcherServlet extends DispatcherServlet {
    
        private final Log logger = LogFactory.getLog(getClass());
    
        @Override
        protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
            long startTime = System.currentTimeMillis();
            try {
                super.doDispatch(request, response);
            } finally {
                log(new ContentCachingRequestWrapper(request), new ContentCachingResponseWrapper(response),
                        System.currentTimeMillis() - startTime);
            }
        }
    
        private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, long timeTaken) {
            int status = responseToCache.getStatus();
            JsonObject jsonObject = new JsonObject();
            jsonObject.addProperty("httpStatus", status);
            jsonObject.addProperty("path", requestToCache.getRequestURI());
            jsonObject.addProperty("httpMethod", requestToCache.getMethod());
            jsonObject.addProperty("timeTakenMs", timeTaken);
            jsonObject.addProperty("clientIP", requestToCache.getRemoteAddr());
            if (status > 299) {
                String requestBody = null;
                try {
                    requestBody = requestToCache.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
                jsonObject.addProperty("requestBody", requestBody);
                jsonObject.addProperty("requestParams", requestToCache.getQueryString());
                jsonObject.addProperty("tokenExpiringHeader",
                        responseToCache.getHeader(ResponseHeaderModifierInterceptor.HEADER_TOKEN_EXPIRING));
            }
            logger.info(jsonObject);
        }
    }
    
  11. ==============================

    11.만약 누군가가 여전히 그것을 필요로한다면 Spring HttpTrace 액츄에이터를 이용한 간단한 구현입니다. 그러나 그들은 상체에게 시체를 기록하지 않는다고 말했습니다.

    만약 누군가가 여전히 그것을 필요로한다면 Spring HttpTrace 액츄에이터를 이용한 간단한 구현입니다. 그러나 그들은 상체에게 시체를 기록하지 않는다고 말했습니다.

    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.builder.ToStringBuilder;
    import org.springframework.boot.actuate.trace.http.HttpTrace;
    import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
    import org.springframework.stereotype.Repository;
    
    @Slf4j
    @Repository
    public class LoggingInMemoryHttpTraceRepository extends InMemoryHttpTraceRepository {
        public void add(HttpTrace trace) {
            super.add(trace);
            log.info("Trace:" + ToStringBuilder.reflectionToString(trace));
            log.info("Request:" + ToStringBuilder.reflectionToString(trace.getRequest()));
            log.info("Response:" + ToStringBuilder.reflectionToString(trace.getResponse()));
        }
    }
    
  12. ==============================

    12.pre-only / post-only 인터셉터의 간소화 된 구현을 위해 커스텀 Spring 인터셉터 인 HandlerInterceptorAdapter를 설정할 수도있다.

    pre-only / post-only 인터셉터의 간소화 된 구현을 위해 커스텀 Spring 인터셉터 인 HandlerInterceptorAdapter를 설정할 수도있다.

    @Component
    public class CustomHttpInterceptor extends HandlerInterceptorAdapter {
    
        @Override
        public boolean preHandle (final HttpServletRequest request, final HttpServletResponse response,
                final Object handler)
                throws Exception {
    
            // Logs here
    
            return super.preHandle(request, response, handler);
        }
    
        @Override
        public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
                final Object handler, final Exception ex) {
            // Logs here
        }
    }
    

    그런 다음 원하는만큼 인터셉터를 등록하십시오.

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Autowired
        CustomHttpInterceptor customHttpInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(customHttpInterceptor).addPathPatterns("/endpoints");
        }
    
    }
    

    참고 : @Robert와 마찬가지로, 응용 프로그램에서 사용하는 HttpServletRequest 및 HttpServletResponse의 특정 구현에주의해야합니다.

  13. ==============================

    13.요청 페이로드의 일부만 보는 경우 setMaxPayloadLength 함수를 호출해야합니다 (기본값은 요청 본문에 50 자만 표시됨). 또한 auth 헤더를 기록하지 않으려면 setIncludeHeaders를 false로 설정하는 것이 좋습니다.

    요청 페이로드의 일부만 보는 경우 setMaxPayloadLength 함수를 호출해야합니다 (기본값은 요청 본문에 50 자만 표시됨). 또한 auth 헤더를 기록하지 않으려면 setIncludeHeaders를 false로 설정하는 것이 좋습니다.

    @Bean
    public CommonsRequestLoggingFilter requestLoggingFilter() {
        CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
        loggingFilter.setIncludeClientInfo(false);
        loggingFilter.setIncludeQueryString(false);
        loggingFilter.setIncludePayload(true);
        loggingFilter.setIncludeHeaders(false);
        loggingFilter.setMaxPayloadLength(500);
        return loggingFilter;
    }
    
  14. ==============================

    14.당신의 부트 애플리케이션에서 Tomcat을 사용한다면 여기에 org.apache.catalina.filters.RequestDumperFilter가있다. (단 하나의 장소에서 예외를 제공하지는 않습니다).

    당신의 부트 애플리케이션에서 Tomcat을 사용한다면 여기에 org.apache.catalina.filters.RequestDumperFilter가있다. (단 하나의 장소에서 예외를 제공하지는 않습니다).

  15. ==============================

    15.실제 답변은 아래 링크를 참조하십시오. https://gist.github.com/int128/e47217bebdb4c402b2ffa7cc199307ba

    실제 답변은 아래 링크를 참조하십시오. https://gist.github.com/int128/e47217bebdb4c402b2ffa7cc199307ba

    위에서 언급 한 솔루션에서 일부 변경을 한 경우 요청 및 응답은 로거 수준이 info 인 경우 콘솔 및 파일에 로그인합니다. 우리는 콘솔이나 파일로 인쇄 할 수 있습니다.

    @Component
    public class LoggingFilter extends OncePerRequestFilter {
    
    private static final List<MediaType> VISIBLE_TYPES = Arrays.asList(
            MediaType.valueOf("text/*"),
            MediaType.APPLICATION_FORM_URLENCODED,
            MediaType.APPLICATION_JSON,
            MediaType.APPLICATION_XML,
            MediaType.valueOf("application/*+json"),
            MediaType.valueOf("application/*+xml"),
            MediaType.MULTIPART_FORM_DATA
            );
    Logger log = LoggerFactory.getLogger(ReqAndResLoggingFilter.class);
    private static final Path path = Paths.get("/home/ramesh/loggerReq.txt");
    private static BufferedWriter writer = null;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            writer = Files.newBufferedWriter(path, Charset.forName("UTF-8"));
        if (isAsyncDispatch(request)) {
            filterChain.doFilter(request, response);
        } else {
            doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
        }
        }finally {
            writer.close();
        }
    }
    
    protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {
        try {
            beforeRequest(request, response);
            filterChain.doFilter(request, response);
        }
        finally {
            afterRequest(request, response);
            response.copyBodyToResponse();
        }
    }
    
    protected void beforeRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) throws IOException {
        if (log.isInfoEnabled()) {
            logRequestHeader(request, request.getRemoteAddr() + "|>");
        }
    }
    
    protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) throws IOException {
        if (log.isInfoEnabled()) {
            logRequestBody(request, request.getRemoteAddr() + "|>");
            logResponse(response, request.getRemoteAddr() + "|<");
        }
    }
    
    private void logRequestHeader(ContentCachingRequestWrapper request, String prefix) throws IOException {
        String queryString = request.getQueryString();
        if (queryString == null) {
            printLines(prefix,request.getMethod(),request.getRequestURI());
            log.info("{} {} {}", prefix, request.getMethod(), request.getRequestURI());
        } else {
            printLines(prefix,request.getMethod(),request.getRequestURI(),queryString);
            log.info("{} {} {}?{}", prefix, request.getMethod(), request.getRequestURI(), queryString);
        }
        Collections.list(request.getHeaderNames()).forEach(headerName ->
        Collections.list(request.getHeaders(headerName)).forEach(headerValue ->
        log.info("{} {}: {}", prefix, headerName, headerValue)));
        printLines(prefix);
        printLines(RequestContextHolder.currentRequestAttributes().getSessionId());
        log.info("{}", prefix);
    
        log.info(" Session ID: ", RequestContextHolder.currentRequestAttributes().getSessionId());
    }
    
    private void printLines(String ...args) throws IOException {
    
        try {
        for(String varArgs:args) {
                writer.write(varArgs);
                writer.newLine();
        }
            }catch(IOException ex){
                ex.printStackTrace();
        }
    
    }
    
    private void logRequestBody(ContentCachingRequestWrapper request, String prefix) {
        byte[] content = request.getContentAsByteArray();
        if (content.length > 0) {
            logContent(content, request.getContentType(), request.getCharacterEncoding(), prefix);
        }
    }
    
    private void logResponse(ContentCachingResponseWrapper response, String prefix) throws IOException {
        int status = response.getStatus();
        printLines(prefix, String.valueOf(status), HttpStatus.valueOf(status).getReasonPhrase());
        log.info("{} {} {}", prefix, status, HttpStatus.valueOf(status).getReasonPhrase());
        response.getHeaderNames().forEach(headerName ->
        response.getHeaders(headerName).forEach(headerValue ->
        log.info("{} {}: {}", prefix, headerName, headerValue)));
        printLines(prefix);
        log.info("{}", prefix);
        byte[] content = response.getContentAsByteArray();
        if (content.length > 0) {
            logContent(content, response.getContentType(), response.getCharacterEncoding(), prefix);
        }
    }
    
    private void logContent(byte[] content, String contentType, String contentEncoding, String prefix) {
        MediaType mediaType = MediaType.valueOf(contentType);
        boolean visible = VISIBLE_TYPES.stream().anyMatch(visibleType -> visibleType.includes(mediaType));
        if (visible) {
            try {
                String contentString = new String(content, contentEncoding);
                Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> {
                    try {
                        printLines(line);
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                });
    //              log.info("{} {}", prefix, line));
            } catch (UnsupportedEncodingException e) {
                log.info("{} [{} bytes content]", prefix, content.length);
            }
        } else {
    
            log.info("{} [{} bytes content]", prefix, content.length);
        }
    }
    
    private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
        if (request instanceof ContentCachingRequestWrapper) {
            return (ContentCachingRequestWrapper) request;
        } else {
            return new ContentCachingRequestWrapper(request);
        }
    }
    
    private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
        if (response instanceof ContentCachingResponseWrapper) {
            return (ContentCachingResponseWrapper) response;
        } else {
            return new ContentCachingResponseWrapper(response);
        }
    }
    } 
    

    파일에서 출력 :

    127.0.0.1|>
    POST
    /createUser
    127.0.0.1|>
    session Id:C0793464532E7F0C7154913CBA018B2B
    Request:
    {
      "name": "asdasdas",
      "birthDate": "2018-06-21T17:11:15.679+0000"
    }
    127.0.0.1|<
    200
    OK
    127.0.0.1|<
    Response:
    {"name":"asdasdas","birthDate":"2018-06-21T17:11:15.679+0000","id":4}
    
  16. ==============================

    16.현재 스프링 부트에는 요청, 응답 로그를 얻기위한 액추에이터 기능이 있습니다.

    현재 스프링 부트에는 요청, 응답 로그를 얻기위한 액추에이터 기능이 있습니다.

    그러나 Aspect를 사용하여 로그를 가져올 수도 있습니다.

    다음은 아래 주어진 Aspect 방법입니다.

    @Before, @AfterReturning, @AfterThrowing 등의 주석을 제공합니다.

    @ 여기에 요청을 로그하기 전에 @ AfterReturning이 응답을 기록하고 @AfterThrowing이 메시지와 함께 오류를 기록하고, 모든 엔드 포인트 로그가 필요하지 않을 수도 있으므로 여기에 패키지 기반의 필터가 있습니다.

    다음은 몇 가지 예입니다.

    요청 :

    @Before("within(your.package.where.is.endpoint..*)")
        public void endpointBefore(JoinPoint p) {
            if (log.isTraceEnabled()) {
                log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
                Object[] signatureArgs = p.getArgs();
    
    
                ObjectMapper mapper = new ObjectMapper();
                mapper.enable(SerializationFeature.INDENT_OUTPUT);
                try {
    
                    if (signatureArgs[0] != null) {
                        log.trace("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
                    }
                } catch (JsonProcessingException e) {
                }
            }
        }
    

    여기 @Before ( "within (your.package.where.is.endpoint .. *)")에는 패키지 경로가 있습니다. 이 패키지 내의 모든 엔드 포인트는 로그를 생성합니다.

    응답 :

    @AfterReturning(value = ("within(your.package.where.is.endpoint..*)"),
                returning = "returnValue")
        public void endpointAfterReturning(JoinPoint p, Object returnValue) {
            if (log.isTraceEnabled()) {
                ObjectMapper mapper = new ObjectMapper();
                mapper.enable(SerializationFeature.INDENT_OUTPUT);
                try {
                    log.trace("\nResponse object: \n" + mapper.writeValueAsString(returnValue));
                } catch (JsonProcessingException e) {
                    System.out.println(e.getMessage());
                }
                log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
            }
        }
    

    여기 @AfterReturning ( "within (your.package.where.is.endpoint .. *)")에는 패키지 경로가 있습니다. 이 패키지 내의 모든 엔드 포인트는 로그를 생성합니다. 또한 객체 returnValue는 응답을가집니다.

    예외 사항 :

    @AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), throwing = "e")
    public void endpointAfterThrowing(JoinPoint p, Exception e) throws DmoneyException {
        if (log.isTraceEnabled()) {
            System.out.println(e.getMessage());
    
            e.printStackTrace();
    
    
            log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
        }
    }
    

    여기 @AfterThrowing (pointcut = ( "within (your.package.where.is.endpoint .. *)"), throwing = "e")에는 패키지 경로가 있습니다. 이 패키지 내의 모든 엔드 포인트는 로그를 생성합니다. 또한 예외 e에는 오류 응답이 있습니다.

    전체 코드는 다음과 같습니다.

    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    import org.apache.log4j.Logger;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Order(1)
    @Component
    @ConditionalOnExpression("${endpoint.aspect.enabled:true}")
    public class EndpointAspect {
        static Logger log = Logger.getLogger(EndpointAspect.class);
    
        @Before("within(your.package.where.is.endpoint..*)")
        public void endpointBefore(JoinPoint p) {
            if (log.isTraceEnabled()) {
                log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
                Object[] signatureArgs = p.getArgs();
    
    
                ObjectMapper mapper = new ObjectMapper();
                mapper.enable(SerializationFeature.INDENT_OUTPUT);
                try {
    
                    if (signatureArgs[0] != null) {
                        log.trace("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
                    }
                } catch (JsonProcessingException e) {
                }
            }
        }
    
        @AfterReturning(value = ("within(your.package.where.is.endpoint..*)"),
                returning = "returnValue")
        public void endpointAfterReturning(JoinPoint p, Object returnValue) {
            if (log.isTraceEnabled()) {
                ObjectMapper mapper = new ObjectMapper();
                mapper.enable(SerializationFeature.INDENT_OUTPUT);
                try {
                    log.trace("\nResponse object: \n" + mapper.writeValueAsString(returnValue));
                } catch (JsonProcessingException e) {
                    System.out.println(e.getMessage());
                }
                log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
            }
        }
    
    
        @AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), throwing = "e")
        public void endpointAfterThrowing(JoinPoint p, Exception e) throws Exception {
            if (log.isTraceEnabled()) {
                System.out.println(e.getMessage());
    
                e.printStackTrace();
    
    
                log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
            }
        }
    }
    

    @ConditionalOnExpression ( "$ {endpoint.aspect.enabled : true}")을 사용하면 로그를 활성화 / 비활성화 할 수 있습니다. application.property에 endpoint.aspect.enabled : true를 추가하고 로그를 제어하십시오.

    AOP에 대한 자세한 정보는 여기를 방문하십시오 :

    AOP에 관한 스프링 부두

    AOP에 관한 샘플 기사

  17. ==============================

    17.여기 내 솔루션 (봄 2.0.x)

    여기 내 솔루션 (봄 2.0.x)

    Maven 종속성을 추가하십시오.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    

    application.properties를 편집하고 다음 행을 추가하십시오.

    management.endpoints.web.exposure.include=* 
    

    스프링 부트 애플리케이션이 시작되면 다음 url을 호출하여 최신 HTTP 요청을 추적 할 수 있습니다. http : // localhost : 8070 / 액추에이터 / httptrace

  18. ==============================

    18.400 만 결과가되는 요청을 기록하려면 다음을 수행하십시오.

    400 만 결과가되는 요청을 기록하려면 다음을 수행하십시오.

    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.apache.commons.io.FileUtils;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.ServletServerHttpRequest;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.filter.AbstractRequestLoggingFilter;
    import org.springframework.web.filter.OncePerRequestFilter;
    import org.springframework.web.util.ContentCachingRequestWrapper;
    import org.springframework.web.util.WebUtils;
    
    /**
     * Implementation is partially copied from {@link AbstractRequestLoggingFilter} and modified to output request information only if request resulted in 400.
     * Unfortunately {@link AbstractRequestLoggingFilter} is not smart enough to expose {@link HttpServletResponse} value in afterRequest() method.
     */
    @Component
    public class RequestLoggingFilter extends OncePerRequestFilter {
    
        public static final String DEFAULT_AFTER_MESSAGE_PREFIX = "After request [";
    
        public static final String DEFAULT_AFTER_MESSAGE_SUFFIX = "]";
    
        private final boolean includeQueryString = true;
        private final boolean includeClientInfo = true;
        private final boolean includeHeaders = true;
        private final boolean includePayload = true;
    
        private final int maxPayloadLength = (int) (2 * FileUtils.ONE_MB);
    
        private final String afterMessagePrefix = DEFAULT_AFTER_MESSAGE_PREFIX;
    
        private final String afterMessageSuffix = DEFAULT_AFTER_MESSAGE_SUFFIX;
    
        /**
         * The default value is "false" so that the filter may log a "before" message
         * at the start of request processing and an "after" message at the end from
         * when the last asynchronously dispatched thread is exiting.
         */
        @Override
        protected boolean shouldNotFilterAsyncDispatch() {
            return false;
        }
    
        @Override
        protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
                throws ServletException, IOException {
    
            final boolean isFirstRequest = !isAsyncDispatch(request);
            HttpServletRequest requestToUse = request;
    
            if (includePayload && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
                requestToUse = new ContentCachingRequestWrapper(request, maxPayloadLength);
            }
    
            final boolean shouldLog = shouldLog(requestToUse);
    
            try {
                filterChain.doFilter(requestToUse, response);
            } finally {
                if (shouldLog && !isAsyncStarted(requestToUse)) {
                    afterRequest(requestToUse, response, getAfterMessage(requestToUse));
                }
            }
        }
    
        private String getAfterMessage(final HttpServletRequest request) {
            return createMessage(request, this.afterMessagePrefix, this.afterMessageSuffix);
        }
    
        private String createMessage(final HttpServletRequest request, final String prefix, final String suffix) {
            final StringBuilder msg = new StringBuilder();
            msg.append(prefix);
            msg.append("uri=").append(request.getRequestURI());
    
            if (includeQueryString) {
                final String queryString = request.getQueryString();
                if (queryString != null) {
                    msg.append('?').append(queryString);
                }
            }
    
            if (includeClientInfo) {
                final String client = request.getRemoteAddr();
                if (StringUtils.hasLength(client)) {
                    msg.append(";client=").append(client);
                }
                final HttpSession session = request.getSession(false);
                if (session != null) {
                    msg.append(";session=").append(session.getId());
                }
                final String user = request.getRemoteUser();
                if (user != null) {
                    msg.append(";user=").append(user);
                }
            }
    
            if (includeHeaders) {
                msg.append(";headers=").append(new ServletServerHttpRequest(request).getHeaders());
            }
    
            if (includeHeaders) {
                final ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
                if (wrapper != null) {
                    final byte[] buf = wrapper.getContentAsByteArray();
                    if (buf.length > 0) {
                        final int length = Math.min(buf.length, maxPayloadLength);
                        String payload;
                        try {
                            payload = new String(buf, 0, length, wrapper.getCharacterEncoding());
                        } catch (final UnsupportedEncodingException ex) {
                            payload = "[unknown]";
                        }
                        msg.append(";payload=").append(payload);
                    }
                }
            }
            msg.append(suffix);
            return msg.toString();
        }
    
        private boolean shouldLog(final HttpServletRequest request) {
            return true;
        }
    
        private void afterRequest(final HttpServletRequest request, final HttpServletResponse response, final String message) {
            if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) {
                logger.warn(message);
            }
        }
    
    }
    
  19. from https://stackoverflow.com/questions/33744875/spring-boot-how-to-log-all-requests-and-responses-with-exceptions-in-single-pl by cc-by-sa and MIT license