Spring Boot - Làm thế nào để ghi nhật ký tất cả các yêu cầu và phản hồi với ngoại lệ ở một nơi duy nhất?


215

Tôi đang làm việc trên api nghỉ ngơi với khởi động mùa xuân. Tôi cần phải ghi nhật ký tất cả các yêu cầu với các tham số đầu vào (với các phương thức, ví dụ: GET, POST, v.v.), đường dẫn yêu cầu, chuỗi truy vấn, phương thức lớp tương ứng của yêu cầu này, cũng phản hồi của hành động này, cả thành công và lỗi.

Ví dụ:

yêu cầu thành công:

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

Nhật ký nên được trông giống như thế này:

{
   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: []       
}

Hoặc yêu cầu có lỗi:

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

Nhật ký nên là một cái gì đó như thế này:

    {
       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: ...................    
       ]       
    }

Tôi muốn Yêu cầu / Phản hồi là một thực thể duy nhất, với thông tin tùy chỉnh liên quan đến thực thể này, cả trong trường hợp thành công và lỗi.

Thực hành tốt nhất vào mùa xuân để đạt được điều này là gì, có thể là với các bộ lọc? nếu có, bạn có thể cung cấp ví dụ cụ thể?

(Tôi đã chơi với @ControllAdvice và @ExceptionHandler, nhưng như tôi đã đề cập, tôi cần xử lý tất cả các yêu cầu thành công và lỗi ở một vị trí (và nhật ký đơn)).


Có thể thông qua một ServletFilter ghi nhật ký (ví dụ stackoverflow.com/a/2171633/995891 ), HandlerInterceptornhưng điều đó có thể không hoạt động tốt với việc ghi lại phản hồi như được đề cập trong câu trả lời: concretepage.com/spring/spring-mvc/ tựa - HandlerInterceptor có quyền truy cập đến phương thức (phương thức: "UsersControll.getUser") mặc dù. Điều đó không được biết đến trong một bộ lọc servlet.
zapl

1
Tuy nhiên, ngay cả khi bạn thêm bộ lọc hoặc bất kỳ giải pháp nào ở lớp ứng dụng, bạn sẽ không ghi lại tất cả yêu cầu, vì Lỗi Máy chủ HTTP 500 sẽ không được ghi lại, vì tại thời điểm đó, một ngoại lệ chưa được xử lý sẽ bị ném vào lớp Ứng dụng, tomcat nhúng mặc định trên trang lỗi sẽ được hiển thị sau khi nuốt ngoại lệ và tất nhiên sẽ không lưu giữ nhật ký. Ngoài ra nếu bạn kiểm tra câu trả lời của user1817243, trong trường hợp có ngoại lệ, anh ta sẽ lại không đăng nhập yêu cầu nhưng anh ta sẽ đăng nhập ngoại lệ (!!).
AntJavaDev

Định dạng nhật ký đó phải phù hợp với mọi ký tự bạn đã viết? Có vẻ như một bản dịch JSON sẽ là tối ưu trong trường hợp của bạn: LogClass{ getRequestAndSaveIt()} Gson.toJson(LogClass)như mã giả
Vale

1
Độc giả tương lai có thể được hưởng lợi từ câu trả lời của tôi (url để theo dõi trong bình luận này). Về cơ bản, tôi đã có thể thẳng thắn cùng nhau đăng các bài viết khác nhau về câu hỏi này. Vui lòng xem xét câu trả lời của bộ truyền động (trong các câu trả lời bên dưới) trước khi thử bằng tay. Nhưng câu trả lời tôi đang đăng cho phép "400, 404, 500" (bất kỳ / tất cả) được ghi lại, nhưng đặt mức độ ưu tiên thứ tự thành mức ưu tiên thấp nhất (hoặc trong "8" nếu bạn xem mã). stackoverflow.com/questions/10210645/ từ
granadaCoder

Tôi đã theo dõi các tài liệu mùa xuân khi đăng nhập từ đây: docs.spring.io/spring-boot/docs/civerse/reference/html/ trộm
T04435

Câu trả lời:


146

Không viết bất kỳ Thiết bị chặn, Bộ lọc, Thành phần, Các khía cạnh, v.v., đây là một vấn đề rất phổ biến và đã được giải quyết nhiều lần.

Spring Boot có một mô-đun gọi là Actuator , cung cấp yêu cầu HTTP đăng xuất khỏi hộp. Có một điểm cuối được ánh xạ tới /trace(SB1.x) hoặc /actuator/httptrace(SB2.0 +) sẽ hiển thị cho bạn 100 yêu cầu HTTP cuối cùng. Bạn có thể tùy chỉnh nó để ghi nhật ký từng yêu cầu hoặc ghi vào DB.

Để có được các điểm cuối mà bạn muốn, bạn sẽ cần phụ thuộc vào bộ khởi động khởi động lò xo khởi động và cũng để "liệt kê" các điểm cuối mà bạn đang tìm kiếm và có thể thiết lập hoặc vô hiệu hóa bảo mật cho nó.

Ngoài ra, ứng dụng này sẽ chạy ở đâu? Bạn sẽ sử dụng PaaS chứ? Nhà cung cấp dịch vụ lưu trữ, Heroku chẳng hạn, cung cấp đăng nhập yêu cầu như một phần của dịch vụ của họ và bạn không cần phải thực hiện bất kỳ mã hóa nào sau đó.


4
còn chi tiết nào nữa không? Tôi đã tìm thấy github.com/spring-projects/spring-boot/tree/master/iêu , nhưng không nhiều trong số đó.
Tom Howard

16
Điều này không thể được sử dụng để gỡ lỗi: các yêu cầu không được xác thực (ví dụ với bảo mật mùa xuân) không được ghi lại.
bekce

11
Trên thực tế, Thiết bị truyền động không có bất kỳ thành phần cụ thể nào để ghi nhật ký http. / dấu vết - chỉ hiển thị N yêu cầu cuối cùng.
Vladimir Filipchenko

18
@ike_love, làm thế nào để xác nhận bộ truyền động sao cho nó ghi nhật ký yêu cầu (cũng là phần thân POST) để gửi?

11
Trace sẽ không đăng nhập yêu cầu và cơ quan phản hồi cho bạn .... mọi thứ khác (tiêu đề, v.v.) nhưng những thứ đó.
Lekkie

94

Spring đã cung cấp một bộ lọc thực hiện công việc này. Thêm bean sau vào cấu hình của bạn

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

Đừng quên để thay đổi mức độ log của org.springframework.web.filter.CommonsRequestLoggingFilterđể DEBUG.


74
Lưu ý rằng nó không ghi lại các câu trả lời, chỉ có các yêu cầu.
Wim Deblauwe

1
Chỉ có yêu cầu. Làm cách nào để ghi nhật ký phản hồi của cơ quan bằng CommonsRequestLoggingFilter?
dùng2602807

3
Ngoài ra, điều này không đăng nhập Ngoại lệ
BhendiGawaar

Vâng, đó là dự kiến ​​vì nó là một bộ lọc đăng nhập yêu cầu. Thêm thông tin về vấn đề này tại đây: docs.spring.io/spring/docs/civerse/javadoc-api/org/ mẹo
Yogesh Badke

4
Nếu bạn có phần thân JSON lớn, hãy đặt độ dài tải trọng thành một số lớn để ghi nhật ký toàn bộ phần thân yêu cầu. logFilter.setMaxPayloadLpm (100000);
Venkatesh Nannan

57

Bạn có thể sử dụng javax.servlet.Filternếu không có yêu cầu ghi nhật ký phương thức java đã được thực thi.

Nhưng với yêu cầu này, bạn phải truy cập thông tin lưu trữ trong handlerMappingcủa DispatcherServlet. Điều đó nói rằng, bạn có thể ghi đè DispatcherServletđể thực hiện ghi nhật ký cặp yêu cầu / phản hồi.

Dưới đây là một ví dụ về ý tưởng có thể được nâng cao hơn nữa và áp dụng cho nhu cầu của bạn.

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 - chứa thông tin về xử lý yêu cầu.

Sau đó, bạn có thể đăng ký điều phối này như sau:

    @Bean
    public ServletRegistrationBean dispatcherRegistration() {
        return new ServletRegistrationBean(dispatcherServlet());
    }

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

Và đây là mẫu nhật ký:

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"}'}

CẬP NHẬT

Trong trường hợp lỗi Spring không xử lý lỗi tự động. Do đó, BasicErrorController#errorđược hiển thị như xử lý yêu cầu. Nếu bạn muốn duy trì trình xử lý yêu cầu ban đầu, thì bạn có thể ghi đè hành vi này spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971trước khi #processDispatchResultđược gọi, để lưu bộ xử lý gốc.


2
Điều gì xảy ra khi phản hồi là một luồng và luồng không hỗ trợ tìm kiếm? Những điều trên vẫn sẽ làm việc?
Tom Howard

Tôi không quan tâm đến phương thức được gọi, chỉ là dữ liệu nhận và gửi. Bộ lọc dường như chỉ cho tôi đi đúng hướng và phản hồi của @ ike_love đã hướng tôi đến github.com/spring-projects/spring-boot/blob/master/ Kẻ
Tom Howard

@TomHoward AFAIK, không có "nhật ký phản hồi" nào trong mùa xuân. Do đó, bạn có thể mở rộng WebRequestTraceFilter hoặc AbstractRequestLoggingFilter thêm logic ghi nhật ký phản hồi.
hahn

Hoạt động tốt!
Pavel Vlasov

@hahn tại sao bạn lại sử dụng Dispatcher servlet cho việc này? Có thể thêm thông tin đăng nhập tương tự với bộ lọc trong doFilter không?
BhendiGawaar

39

Các nhật ký thư viện được đặc biệt làm cho các yêu cầu HTTP khai thác gỗ và câu trả lời. Nó hỗ trợ Spring Boot bằng thư viện khởi động đặc biệt.

Để cho phép đăng nhập vào Spring Boot, tất cả những gì bạn cần làm là thêm thư viện vào các phụ thuộc của dự án. Ví dụ: giả sử bạn đang sử dụng Maven:

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

Theo mặc định, đầu ra đăng nhập trông như thế này:

{
  "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"
}

Tuy nhiên, nó không xuất ra tên lớp đang xử lý yêu cầu. Thư viện có một số giao diện để viết logger tùy chỉnh.


4
được thêm vào như một phụ thuộc vào một ứng dụng khởi động mùa xuân tối thiểu và cố gắng chạy - không thay đổi, không có đầu ra đăng nhập nào trong ứng dụng của tôi. Tôi nghĩ rằng có một số phụ thuộc bổ sung hoặc các lớp này cần? Đăng ký nó như một bộ lọc dường như không làm gì cả.
eis

1
@eis Bạn cần đăng ký nó như một bộ lọc như được giải thích trong các tài liệu ở đây. github.com/zalando/logbook
Pratik Singhal

1
Nhật ký tài liệu nói: "Nhật ký đi kèm với cấu hình tự động thuận tiện cho người dùng Spring Boot. Nó tự động thiết lập tất cả các phần sau với mặc định hợp lý." Nhưng nó không hoạt động.
Leos Literak

5
@LeosLiterak Tôi tin rằng bạn cần thêm logging.level.org.zalando.logbook=TRACE vào application.properties(như đã nêu trong Readme)
TolkienWASP

2
Nhật ký tự động cấu hình dường như không hoạt động cho spring-boot v2.0.5
Yashveer Rana

26

Tôi đã xác định mức ghi nhật ký application.propertiesđể in yêu cầu / phản hồi, url phương thức trong tệp nhật ký

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

Tôi đã sử dụng Spring Boot.


2
Có, bạn đã đúng - đây là câu trả lời hợp lệ để nhận yêu cầu đăng nhập vào cùng một tệp nhật ký với tất cả các kết quả khác. Tuy nhiên, @moreo đã yêu cầu đăng nhập GET, POST, v.v. và vào tệp riêng (theo tôi hiểu)
Manushin Igor

4
Tôi thích cái này. kịch không
Quirino Gervacio

1
Nếu bạn muốn các tiêu đề được bao gồm trong nhật ký, thì bạn nên thêm: "spring.http.log-request-chi tiết = true" vào tệp application.properations của bạn.
jfajunior

20

Đây là cách tôi làm trong phần còn lại của dữ liệu mùa xuân bằng cách sử dụng org.springframework.web.util.ContentCachingRequestWrapperorg.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

  }


}

18

Nếu bạn không ngại dùng thử Spring AOP, đây là thứ tôi đã khám phá cho mục đích đăng nhập và nó hoạt động khá tốt đối với tôi. Nó sẽ không đăng nhập các yêu cầu chưa được xác định và các yêu cầu thất bại mặc dù.

Thêm ba phụ thuộc này

spring-aop, aspectjrt, aspectjweaver

Thêm phần này vào tập tin cấu hình xml của bạn <aop:aspectj-autoproxy/>

Tạo một chú thích có thể được sử dụng như một điểm cắt

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

Bây giờ chú thích tất cả các phương thức API còn lại mà bạn muốn đăng nhập

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

Bây giờ đến khía cạnh. quét thành phần gói mà lớp này đang ở.

@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.
    }
}

Lời khuyên @AfterReturn chạy khi thực thi phương thức khớp trở lại bình thường.

Lời khuyên @AfterThrowing chạy khi thực thi phương thức phù hợp thoát bằng cách ném ngoại lệ.

Nếu bạn muốn đọc chi tiết hãy đọc qua điều này. http://docs.spring.io/spring/docs/cản/spring-framework-reference/html/aop.html


1
Điều này ghi lại lời gọi phương thức, không phải những gì thực sự nhận và gửi ở cấp HTTP.
Tom Howard

1
Làm thế nào để viết yêu cầu CƠ THỂ? Trong trường hợp của tôi đó là POST CƠ THỂ. trên request.getReader hoặc getInputStream Tôi gặp lỗi khi luồng bị đóng.

13

Sau khi thêm Thiết bị truyền động vào ứng dụng bass khởi động mùa xuân, bạn có /tracesẵn điểm cuối với thông tin yêu cầu mới nhất. Điểm cuối này hoạt động dựa trên TraceRep repository và cài đặt mặc định là InMemoryTraceRep repository giúp lưu 100 cuộc gọi gần nhất. Bạn có thể thay đổi điều này bằng cách tự mình thực hiện giao diện này và làm cho nó có sẵn dưới dạng Spring bean. Ví dụ: để ghi nhật ký tất cả các yêu cầu để đăng nhập (và vẫn sử dụng triển khai mặc định làm bộ lưu trữ cơ bản để cung cấp thông tin trên /traceđiểm cuối) Tôi đang sử dụng loại triển khai này:

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);
  }
}

Đây traceInfobản đồ chứa thông tin cơ bản về yêu cầu và phản ứng bằng hiện vật này có dạng: {method=GET, path=/api/hello/John, headers={request={host=localhost:8080, user-agent=curl/7.51.0, accept=*/*}, response={X-Application-Context=application, Content-Type=text/plain;charset=UTF-8, Content-Length=10, Date=Wed, 29 Mar 2017 20:41:21 GMT, status=200}}}. KHÔNG có nội dung trả lời ở đây.

BIÊN TẬP! Ghi nhật ký dữ liệu POST

Bạn có thể truy cập dữ liệu POST bằng cách ghi đè WebRequestTraceFilter , nhưng đừng nghĩ rằng đó là một ý tưởng hay (ví dụ: tất cả nội dung tệp được tải lên sẽ vào nhật ký) Đây là mã mẫu, nhưng không sử dụng nó:

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;
  }
}

1
Thế còn POST?
Pavel Vyazankin

@dart Tôi đã thêm ví dụ cho bạn
Piotr Chowaniec

1
Tôi đã làm một cái gì đó như thế này, nhưng vấn đề là cơ quan phản hồi không có sẵn TraceRepository, làm thế nào chúng ta có thể truy cập vào đó?
Amir Pashazadeh

@AmirPashazadeh bạn phải ghi đè protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)nhưng tôi không chắc chắn khi bộ lọc này được thực thi - có thể đang trong giai đoạn yêu cầu, vì vậy cơ quan phản hồi sẽ không sẵn sàng ở đó.
Piotr Chowaniec

1
@Kekar ​​Kể từ 2.0, có httpTraceRep repository (thay vì TraceRep repository)
Piotr Chowaniec

12

Mã này hoạt động với tôi trong ứng dụng Spring Boot - chỉ cần đăng ký nó dưới dạng bộ lọc

    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();
            }

        }
    }

Điều này hoạt động tốt để ghi nhật ký phản hồi - mặc dù tôi phải đặt giới hạn về số lượng byte mà nó ghi nếu không nó sẽ bỏ qua đầu ra của bảng điều khiển ghi nhật ký Intellij.
Adam

Chuỗi getContent () {if (bos == null) {return String.format ("được gọi là% s quá sớm", BufferedResponseWrapper. Class.getCanonicalName ()); } byte [] byte = bos.toByteArray (); trả về Chuỗi mới (Arrays.copyOf (byte, 5000)) + "...."; }
Adam

Cũng đáng để đặt một công tắc "log.isTraceEnables ()" xung quanh việc ghi nhật ký.
Adam

6
Điều tuyệt vời là nếu Java đã thêm một số phương thức mặc định vào HttpServletResponse để chúng ta không cần phải viết một triển khai lớn như vậy.
Adam

1
cộng với một để bao gồm các báo cáo nhập khẩu
granadaCoder

7

Đây là giải pháp của tôi (Spring 2.0.x)

Thêm phụ thuộc maven:

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

Chỉnh sửa application.properations và thêm dòng sau:

management.endpoints.web.exposure.include=* 

Khi ứng dụng khởi động mùa xuân của bạn được khởi động, bạn có thể theo dõi 100 yêu cầu http mới nhất bằng cách gọi url này: http: // localhost: 8070 / thiết bị truyền động / omeptrace


7

Hiện tại Spring Boot có tính năng Actuator để lấy nhật ký yêu cầu và phản hồi.

Nhưng bạn cũng có thể lấy nhật ký bằng Aspect (AOP).

Aspect cung cấp cho bạn với các chú thích như: @Before, @AfterReturning, @AfterThrowing, vv

@Beforeghi nhật ký yêu cầu, @AfterReturningghi nhật ký phản hồi và @AfterThrowingghi thông báo lỗi, Bạn có thể không cần nhật ký của tất cả các điểm cuối, vì vậy bạn có thể áp dụng một số bộ lọc trên các gói.

Dưới đây là một số ví dụ :

Cho yêu cầu:

@Before("within(your.package.where.endpoints.are..*)")
    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) {
            }
        }
    }

Ở đây @Before("within(your.package.where.endpoints.are..*)")có đường dẫn gói. Tất cả các điểm cuối trong gói này sẽ tạo ra nhật ký.

Để trả lời:

@AfterReturning(value = ("within(your.package.where.endpoints.are..*)"),
            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");
        }
    }

Ở đây @AfterReturning("within(your.package.where.endpoints.are..*)")có đường dẫn gói. Tất cả các điểm cuối trong gói này sẽ tạo ra nhật ký. Cũng Object returnValuechứa các phản ứng.

Đối với ngoại lệ:

@AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), 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());
    }
}

Ở đây @AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e") có đường dẫn gói. Tất cả các điểm cuối trong gói này sẽ tạo ra nhật ký. Cũng Exception echứa các phản ứng lỗi.

Đây là mã đầy đủ:

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());
        }
    }
}

Tại đây, sử dụng @ConditionalOnExpression("${endpoint.aspect.enabled:true}")bạn có thể bật / tắt nhật ký. chỉ cần thêm endpoint.aspect.enabled:truevào application.propertyvà kiểm soát nhật ký

Thông tin thêm về AOP truy cập tại đây:

Mùa xuân cập bến về AOP

Bài viết mẫu về AOP


1
new ObjectMapper()là đắt tiền, chia sẻ tốt hơn một người lập bản đồ cho tất cả mọi người
Sam

Ừ chắc chắn. Đây là mã demo. Trong sản xuất chúng ta phải tuân theo các thực hành tốt nhất.
Md. Sajedul Karim

5

Bạn cũng có thể định cấu hình bộ chặn Mùa xuân tùy chỉnh HandlerInterceptorAdapterđể triển khai đơn giản các bộ chặn chặn chỉ trước / sau chỉ:

@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
    }
}

Sau đó, bạn đăng ký nhiều tên chặn như bạn muốn:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    CustomHttpInterceptor customHttpInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(customHttpInterceptor).addPathPatterns("/endpoints");
    }

}

Lưu ý: giống như được nêu bởi @Robert , bạn cần chú ý đến việc triển khai cụ thể ứng dụng của bạn đang sử dụng. HttpServletRequestHttpServletResponse

Ví dụ: đối với các ứng dụng sử dụng ShallowEtagHeaderFilter, việc triển khai phản hồi sẽ là một ContentCachingResponseWrapper, vì vậy bạn có:

@Component
public class CustomHttpInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomHttpInterceptor.class);

    private static final int MAX_PAYLOAD_LENGTH = 1000;

    @Override
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
            final Object handler, final Exception ex) {
        final byte[] contentAsByteArray = ((ContentCachingResponseWrapper) response).getContentAsByteArray();

        LOGGER.info("Request body:\n" + getContentAsString(contentAsByteArray, response.getCharacterEncoding()));
    }

    private String getContentAsString(byte[] buf, String charsetName) {
        if (buf == null || buf.length == 0) {
            return "";
        }

        try {
            int length = Math.min(buf.length, MAX_PAYLOAD_LENGTH);

            return new String(buf, 0, length, charsetName);
        } catch (UnsupportedEncodingException ex) {
            return "Unsupported Encoding";
        }
    }

}

4

Câu trả lời của @ hahn yêu cầu một chút sửa đổi để nó hoạt động với tôi, nhưng đó là điều tùy biến nhất mà tôi có thể nhận được.

Nó không hoạt động với tôi, có lẽ vì tôi cũng có HandlerInterceptorAd CHƯƠNG [??] nhưng tôi vẫn nhận được phản hồi không tốt từ máy chủ trong phiên bản đó. Đây là sửa đổi của tôi về nó.

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);
    }
}

ứng dụng của bạn được đóng gói như chiến tranh hay bình? Tôi liên tục gặp lỗi java.io.FileNotFoundException: Không thể mở tài nguyên ServletContext [/weB-INF/loggingDispatcherServlet-servlet.xml]
Mayank Madhav

4

Nếu ai đó vẫn cần nó ở đây là triển khai đơn giản với Bộ truyền động Spring httpTrace. Nhưng như họ đã nói ở trên, nó không đăng nhập cơ thể.

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()));
    }
}

4

Vui lòng tham khảo liên kết dưới đây để biết câu trả lời thực tế https://gist.github.com/int128/e47217bebdb4c402b2ffa7cc199307ba

Thực hiện một số thay đổi từ giải pháp được đề cập ở trên, yêu cầu và phản hồi sẽ đăng nhập vào bảng điều khiển và trong tệp cũng vậy nếu mức logger là thông tin. chúng ta có thể in hoặc trong giao diện điều khiển hoặc tập tin.

@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);
    }
}
} 

Đầu ra trong tệp:

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}

1
Câu trả lời tuyệt vời, chỉ có đề xuất là thu thập tất cả đầu ra vào một bộ đệm và đăng nhập vào một câu lệnh.
Mike

2

Nếu bạn chỉ thấy một phần trọng tải yêu cầu của mình, bạn cần gọi setMaxPayloadLengthhàm vì nó mặc định chỉ hiển thị 50 ký tự trong thân yêu cầu của bạn. Ngoài ra, đặt setIncludeHeadersthành false là một ý tưởng hay nếu bạn không muốn đăng nhập tiêu đề xác thực của mình!

@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;
}

Tôi đang cố gắng sử dụng nó trong spring mvc và nó không hoạt động đối với tôi. Nó được yêu cầu bất kỳ cài đặt bổ sung nào ngoại trừ đăng ký bean này và thêm logger?
Noman Akhtar

1

nếu bạn sử dụng Tomcat trong ứng dụng khởi động thì đây là org.apache.catalina.filters.RequestDumperFiltermột đường dẫn dành cho bạn. (nhưng nó sẽ không cung cấp cho bạn "ngoại lệ ở một nơi duy nhất").


1

mã được dán bên dưới hoạt động với các thử nghiệm của tôi và có thể được tải xuống từ [dự án github] [1] của tôi, chia sẻ sau khi áp dụng giải pháp dựa trên dự án sản xuất đó.

@Configuration
public class LoggingFilter extends GenericFilterBean {

    /**
     * It's important that you actually register your filter this way rather then just annotating it
     * as @Component as you need to be able to set for which "DispatcherType"s to enable the filter
     * (see point *1*)
     * 
     * @return
     */
    @Bean
    public FilterRegistrationBean<LoggingFilter> initFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new LoggingFilter());

        // *1* make sure you sett all dispatcher types if you want the filter to log upon
        registrationBean.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));

        // *2* this should put your filter above any other filter
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return registrationBean;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        ContentCachingRequestWrapper wreq = 
            new ContentCachingRequestWrapper(
                (HttpServletRequest) request);

        ContentCachingResponseWrapper wres = 
            new ContentCachingResponseWrapper(
                (HttpServletResponse) response);

        try {

            // let it be ...
            chain.doFilter(wreq, wres);

            // makes sure that the input is read (e.g. in 404 it may not be)
            while (wreq.getInputStream().read() >= 0);

            System.out.printf("=== REQUEST%n%s%n=== end request%n",
                    new String(wreq.getContentAsByteArray()));

            // Do whatever logging you wish here, in this case I'm writing request 
            // and response to system out which is probably not what you wish to do
            System.out.printf("=== RESPONSE%n%s%n=== end response%n",
                    new String(wres.getContentAsByteArray()));

            // this is specific of the "ContentCachingResponseWrapper" we are relying on, 
            // make sure you call it after you read the content from the response
            wres.copyBodyToResponse();

            // One more point, in case of redirect this will be called twice! beware to handle that
            // somewhat

        } catch (Throwable t) {
            // Do whatever logging you whish here, too
            // here you should also be logging the error!!!
            throw t;
        }

    }
}

0

Để ghi nhật ký tất cả các yêu cầu với tham số đầu vào và phần thân, chúng ta có thể sử dụng các bộ lọcbộ chặn . Nhưng trong khi sử dụng bộ lọc hoặc bộ chặn, chúng tôi không thể in phần thân yêu cầu nhiều lần. Cách tốt hơn là chúng ta có thể sử dụng spring-AOP. Bằng cách sử dụng này, chúng ta có thể tách rời cơ chế ghi nhật ký khỏi ứng dụng. AOP có thể được sử dụng để ghi nhật ký Đầu vào và đầu ra của từng phương thức trong ứng dụng.

Giải pháp của tôi là:

 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Pointcut;
 import org.aspectj.lang.reflect.CodeSignature;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 import com.fasterxml.jackson.databind.ObjectMapper;
 @Aspect
 @Component
public class LoggingAdvice {
private static final Logger logger = 
LoggerFactory.getLogger(LoggingAdvice.class);

//here we can provide any methodName, packageName, className 
@Pointcut(value = "execution(* com.package.name.*.*.*(..) )")
public void myPointcut() {

}

@Around("myPointcut()")
public Object applicationLogger(ProceedingJoinPoint pjt) throws Throwable {
    ObjectMapper mapper = new ObjectMapper();
    String methodName = pjt.getSignature().getName();
    String className = pjt.getTarget().getClass().toString();
    String inputParams = this.getInputArgs(pjt ,mapper);
    logger.info("method invoked from " + className + " : " + methodName + "--Request Payload::::"+inputParams);
    Object object = pjt.proceed();
    try {
        logger.info("Response Object---" + mapper.writeValueAsString(object));
    } catch (Exception e) {
    }
    return object;
}

private String getInputArgs(ProceedingJoinPoint pjt,ObjectMapper mapper) {
    Object[] array = pjt.getArgs();
    CodeSignature signature = (CodeSignature) pjt.getSignature();

    StringBuilder sb = new StringBuilder();
    sb.append("{");
    int i = 0;
    String[] parameterNames = signature.getParameterNames();
    int maxArgs = parameterNames.length;
    for (String name : signature.getParameterNames()) {
        sb.append("[").append(name).append(":");
        try {
            sb.append(mapper.writeValueAsString(array[i])).append("]");
            if(i != maxArgs -1 ) {
                sb.append(",");
            }
        } catch (Exception e) {
            sb.append("],");
        }
        i++;
    }
    return sb.append("}").toString();
}

}


0

Nếu bạn đã cấu hình máy chủ Spring boot Config thì chỉ cần bật trình gỡ lỗi Debug cho lớp:

Http11InputBuffer.Http11InputBuffer.java

Các lỗi sẽ ghi lại tất cả các yêu cầu và phản hồi cho mọi yêu cầu


-1

Để ghi nhật ký các yêu cầu chỉ có 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);
        }
    }

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.