Spring Boot - วิธีบันทึกคำขอและคำตอบทั้งหมดโดยมีข้อยกเว้นในที่เดียว?


216

ฉันกำลังทำงานกับส่วนที่เหลือ API กับฤดูใบไม้ผลิบูต ฉันต้องการบันทึกคำขอทั้งหมดด้วย params การป้อนข้อมูล (ด้วยวิธีการเช่น 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 แต่ตามที่ฉันได้กล่าวมาแล้วฉันต้องจัดการกับคำขอความสำเร็จและข้อผิดพลาดทั้งหมดในที่เดียว (และบันทึกเดียว)


อาจผ่านการบันทึก ServletFilter (เช่นstackoverflow.com/a/2171633/995891 ) หรือHandlerInterceptorอาจทำงานได้ไม่ดีกับการบันทึกการตอบสนองตามที่กล่าวไว้ในคำตอบ: concretepage.com/spring/spring-mvc/… - HandlerInterceptor มีการเข้าถึง กับวิธีการ (เมธอด: "UsersController.getUser") ถึงแม้ว่า ไม่รู้จักในตัวกรองเซิร์ฟเล็ต
zapl

1
ยังแม้ว่าคุณจะเพิ่มตัวกรองหรือวิธีการแก้ปัญหาใด ๆ ที่ชั้นแอปพลิเคชันคุณจะไม่บันทึกคำขอทั้งหมดและข้อผิดพลาด HTTP 500 Server จะไม่ได้รับการบันทึกสาเหตุในเวลาที่เกิดข้อยกเว้นที่ไม่สามารถจัดการได้ที่ชั้นแอปพลิเคชัน Tomcat ฝังตัวเริ่มต้นในหน้าข้อผิดพลาดจะปรากฏขึ้นหลังจากกลืนข้อยกเว้นและ ofcourse จะไม่เก็บบันทึก นอกจากนี้หากคุณตรวจสอบคำตอบของผู้ใช้ 1817243 ในกรณีที่มีข้อยกเว้นใด ๆ เขาจะไม่บันทึกคำขออีกครั้ง แต่เขาจะบันทึกข้อยกเว้น (!!)
AntJavaDev

รูปแบบบันทึกนั้นต้องสอดคล้องกับตัวละครทุกตัวที่คุณเขียนหรือไม่? ดูเหมือนว่าการแปล JSON จะเหมาะสมที่สุดในกรณีของคุณ: LogClass{ getRequestAndSaveIt()} Gson.toJson(LogClass)เป็น pseudocode
Vale

1
ผู้อ่านในอนาคตอาจได้รับประโยชน์จากคำตอบของฉัน (url เพื่อติดตามในความคิดเห็นนี้) โดยพื้นฐานแล้วฉันสามารถพูดตรงไปตรงมาโพสต์ที่แตกต่างกันเกี่ยวกับคำถามนี้ โปรดพิจารณาคำตอบของแอคชูเอเตอร์ (ในคำตอบด้านล่าง) ก่อนลองด้วยมือ แต่คำตอบที่ฉันโพสต์อนุญาตให้ "400, 404, 500" (ใด ๆ / ทั้งหมด) เข้าสู่ระบบ แต่การตั้งค่าลำดับความสำคัญเป็นลำดับความสำคัญต่ำสุด (หรือภายในโดย "8" ถ้าคุณดูรหัส) stackoverflow.com/questions/10210645/…
granadaCoder

ฉันทำตามเอกสารฤดูใบไม้ผลิเมื่อเข้าสู่ระบบจากที่นี่: docs.spring.io/spring-boot/docs/current/reference/html/ …
T04435

คำตอบ:


148

อย่าเขียน Interceptors, Filter, Components, Aspects ฯลฯ ใด ๆ นี่เป็นปัญหาที่พบบ่อยมากและได้รับการแก้ไขหลายครั้ง

Spring Boot มีโมดูลที่เรียกว่าActuatorซึ่งให้การร้องขอ HTTP ออกจากกล่อง มีจุดปลายที่จับคู่กับ/trace(SB1.x) หรือ/actuator/httptrace(SB2.0 +) ซึ่งจะแสดงคำขอ HTTP ล่าสุด 100 รายการ คุณสามารถปรับแต่งมันเพื่อบันทึกคำขอแต่ละครั้งหรือเขียนไปยังฐานข้อมูล

ในการรับจุดปลายที่คุณต้องการคุณจะต้องมีการขึ้นต่อกันของ spring-boot-starter-actuator และรวมถึง "รายการที่อนุญาต" จุดสิ้นสุดที่คุณกำลังมองหาและอาจตั้งค่าหรือปิดใช้งานการรักษาความปลอดภัย

แอปพลิเคชันนี้จะทำงานที่ไหน คุณจะใช้ PaaS หรือไม่? โฮสติ้งผู้ให้บริการ Heroku เช่นให้คำขอเข้าสู่ระบบเป็นส่วนหนึ่งของบริการของพวกเขาและคุณไม่ต้องทำใด ๆการเข้ารหัสใด ๆ แล้ว


4
รายละเอียดเพิ่มเติมใด ๆ ฉันพบgithub.com/spring-projects/spring-boot/tree/master/…แต่ไม่มากไปกว่านั้น
Tom Howard

16
สิ่งนี้ไม่สามารถใช้สำหรับการดีบัก: คำขอที่ไม่ได้รับการรับรองความถูกต้อง (ตัวอย่างเช่นการรักษาความปลอดภัยแบบสปริง) จะไม่ได้รับการบันทึก
bekce

11
จริงๆแล้วแอคทูเอเตอร์ไม่มีส่วนประกอบเฉพาะสำหรับการบันทึกข้อมูล http / trace - แสดงเฉพาะการร้องขอ N ครั้งล่าสุด
Vladimir Filipchenko

18
@ike_love ทำอย่างไรจึงจะทำให้แอคชูเอเตอร์ทำงานเช่นนั้นเพื่อบันทึกคำขอ (รวมถึงเนื้อหาของ POST) ไปยังไฟล์

11
การติดตามจะไม่บันทึกคำขอและเนื้อหาตอบกลับสำหรับคุณ .... ทุกอย่างอื่น (ส่วนหัวและอื่น ๆ ) ยกเว้นสิ่งเหล่านั้น
Lekkie

94

สปริงมีตัวกรองที่ทำงานนี้แล้ว เพิ่ม bean ต่อไปนี้เข้ากับ config ของคุณ

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

อย่าลืมที่จะเปลี่ยนระดับการบันทึกของการorg.springframework.web.filter.CommonsRequestLoggingFilterDEBUG


75
โปรดทราบว่ามันไม่ได้บันทึกการตอบสนองเพียงการร้องขอ
Wim Deblauwe

1
มีคำขอเท่านั้น วิธีบันทึกการตอบสนองเนื้อความโดยใช้ CommonsRequestLoggingFilter
user2602807

3
นอกจากนี้ยังไม่ได้บันทึกข้อยกเว้น
BhendiGawaar

คาดว่าเป็นเพราะมันเป็นตัวกรองการบันทึกคำขอ เพิ่มเติมเกี่ยวกับเรื่องนี้ที่นี่: docs.spring.io/spring/docs/current/javadoc-api/org/…
Yogesh Badke

4
หากคุณมีเนื้อความ JSON ขนาดใหญ่ให้ตั้งความยาวส่วนของข้อมูลเป็นจำนวนมากเพื่อบันทึกเนื้อหาของคำขอทั้งหมด loggingFilter.setMaxPayloadLength (100000);
Venkatesh Nannan

58

คุณสามารถใช้javax.servlet.Filterหากไม่มีข้อกำหนดในการบันทึกวิธีการจาวาที่ถูกดำเนินการ

แต่ด้วยความต้องการนี้คุณจะต้องเข้าถึงข้อมูลที่เก็บไว้ในของhandlerMapping DispatcherServletที่กล่าวว่าคุณสามารถแทนที่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"}'}

UPDATE

ในกรณีที่มีข้อผิดพลาดสปริงจะจัดการข้อผิดพลาดอัตโนมัติ ดังนั้นBasicErrorController#errorจะแสดงเป็นตัวจัดการการร้องขอ หากคุณต้องการเก็บรักษาตัวจัดการคำร้องขอดั้งเดิมคุณสามารถแทนที่ลักษณะการทำงานนี้ที่spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971ก่อนหน้า#processDispatchResultนี้เรียกว่าเพื่อแคชตัวจัดการดั้งเดิม


2
จะเกิดอะไรขึ้นเมื่อการตอบสนองเป็นกระแสและสตรีมไม่สนับสนุนการค้นหา ข้างต้นจะยังคงทำงานหรือไม่
Tom Howard

ฉันไม่สนใจวิธีการที่เรียกใช้เพียงข้อมูลที่ได้รับและส่ง ตัวกรองดูเหมือนจะชี้ให้ฉันในทิศทางที่ถูกต้องและการตอบสนองของ @ ike_love ทำให้ฉันไปที่github.com/spring-projects/spring-boot/blob/master/ …
Tom Howard

@ TomHoward AFAIK ไม่มี "การบันทึกการตอบกลับ" ในฤดูใบไม้ผลิ ดังนั้นคุณสามารถขยาย WebRequestTraceFilter หรือ AbstractRequestLoggingFilter เพิ่มตรรกะการบันทึกการตอบสนอง
hahn

ทำงานได้ดี!
Pavel Vlasov

@ ฮาห์นทำไมคุณถึงใช้ Dispatcher servlet เพื่อสิ่งนี้? สามารถเข้าสู่ระบบเดียวกันไม่สามารถเพิ่มด้วยตัวกรองใน doFilter?
BhendiGawaar

39

Logbookห้องสมุดจะทำเฉพาะสำหรับการร้องขอการเข้าสู่ระบบ HTTP และการตอบสนอง รองรับ Spring Boot โดยใช้ไลบรารีเริ่มต้นพิเศษ

ในการเปิดใช้งานการเข้าสู่ระบบใน 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"
}

อย่างไรก็ตามจะไม่ส่งออกชื่อคลาสที่จัดการคำขอ ไลบรารีมีอินเตอร์เฟสบางส่วนสำหรับการเขียนตัวบันทึกที่กำหนดเอง


4
เพิ่มเป็นการอ้างอิงไปยังแอพ spring boot ขั้นต่ำและพยายามเรียกใช้ - ไม่มีการเปลี่ยนแปลงไม่มีการบันทึกผลเลยในแอพของฉัน ฉันคิดว่ามีการพึ่งพาเพิ่มเติมหรือชั้นเรียนนี้ต้องการ? การลงทะเบียนเป็นตัวกรองดูเหมือนจะไม่ทำอะไรเลย
eis

1
@eis คุณต้องลงทะเบียนเป็นตัวกรองตามที่อธิบายไว้ในเอกสารที่นี่ github.com/zalando/logbook
Pratik Singhal

2
Logbook doc กล่าวว่า: "Logbook มาพร้อมกับการกำหนดค่าอัตโนมัติที่สะดวกสบายสำหรับผู้ใช้ Spring Boot โดยจะตั้งค่าทุกส่วนต่อไปนี้โดยอัตโนมัติด้วยค่าเริ่มต้นที่สมเหตุสมผล" แต่มันไม่ทำงาน
Leos Literak

5
@LeosLiterak ฉันเชื่อว่าคุณต้องเพิ่มlogging.level.org.zalando.logbook=TRACE ในของคุณapplication.properties(ตามที่ระบุไว้ในReadme)
TolkienWASP

2
สมุดจดรายการอัตโนมัติการกำหนดรูปแบบดูเหมือนจะไม่ทำงานสำหรับ spring-boot v2.0.5
Yashveer Rana

26

ฉันได้กำหนดระดับการเข้าสู่ระบบapplication.propertiesเพื่อพิมพ์คำร้องขอ / การตอบกลับ, เมธอด url ในล็อกไฟล์

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

ฉันใช้ Spring Boot


2
ใช่คุณพูดถูก - นี่คือคำตอบที่ถูกต้องสำหรับการร้องขอการบันทึกลงในไฟล์บันทึกเดียวกันกับผลลัพธ์อื่น ๆ ทั้งหมด อย่างไรก็ตาม @moreo ขอให้เข้าสู่ระบบ GET, POST, ฯลฯ และไปยังไฟล์แยกต่างหาก (เท่าที่ฉันเข้าใจ)
Manushin Igor

4
ฉันชอบอันนี้ zero drama
Quirino Gervacio

1
หากคุณต้องการให้รวมส่วนหัวในบันทึกดังนั้นคุณควรเพิ่ม: "spring.http.log-request-details = true" ลงในไฟล์ application.properties ของคุณ
jfajunior

20

นี่คือวิธีที่ฉันทำในส่วนที่เหลือของข้อมูลฤดูใบไม้ผลิโดยใช้ 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

  }


}

18

ถ้าคุณไม่ลองใช้ Spring AOP นี่เป็นสิ่งที่ฉันได้ทำการสำรวจเพื่อการบันทึกและมันก็ใช้ได้ดีสำหรับฉัน มันจะไม่บันทึกคำขอที่ยังไม่ได้กำหนดและไม่พยายามตามคำขอ

เพิ่มการพึ่งพาทั้งสามนี้

spring-aop, aspectjrt, aspectjweaver

เพิ่มลงในไฟล์กำหนดค่า xml ของคุณ <aop:aspectj-autoproxy/>

สร้างคำอธิบายประกอบที่สามารถใช้เป็นจุดตัดได้

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

@AfterReturning คำแนะนำจะทำงานเมื่อการดำเนินการตามวิธีที่ตรงกันกลับมาตามปกติ

@AfterThrowing คำแนะนำจะทำงานเมื่อมีการเรียกใช้เมธอดที่ตรงกันโดยการโยนข้อยกเว้น

หากคุณต้องการอ่านรายละเอียดอ่านผ่านสิ่งนี้ http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html


1
สิ่งนี้บันทึกการเรียกใช้เมธอดไม่ใช่สิ่งที่ได้รับและส่งจริงที่ระดับ HTTP
Tom Howard

1
จะเขียนคำขอ BODY ได้อย่างไร? ในกรณีของฉันมันเป็น POST BODY ที่ request.getReader หรือ getInputStream ฉันได้รับข้อผิดพลาดที่กระแสข้อมูลถูกปิด

13

หลังจากเพิ่มแอคชูเอเตอร์ลงในแอปพลิเคชั่นตัวบู๊ตสปริงบูสต์คุณมี/traceอุปกรณ์ปลายทางพร้อมข้อมูลการร้องขอล่าสุด ปลายทางนี้ทำงานตามTraceRepositoryและการใช้งานเริ่มต้นคือInMemoryTraceRepositoryที่บันทึกการโทร 100 ครั้งล่าสุด คุณสามารถเปลี่ยนแปลงสิ่งนี้ได้โดยการใช้อินเทอร์เฟซนี้ด้วยตัวเองและทำให้มันเป็น Spring bean ตัวอย่างเช่นการบันทึกคำขอทั้งหมดเพื่อเข้าสู่ระบบ (และยังคงใช้การใช้งานเริ่มต้นเป็นที่เก็บข้อมูลพื้นฐานสำหรับการให้บริการข้อมูลใน/traceจุดสิ้นสุด) ฉันกำลังใช้งานประเภทนี้:

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={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}}}ไม่มีเนื้อหาการตอบสนองที่นี่

แก้ไข! กำลังบันทึกข้อมูล POST

คุณสามารถเข้าถึงข้อมูล POST ได้โดยการเอาชนะWebRequestTraceFilterแต่อย่าคิดว่ามันเป็นความคิดที่ดี (เช่นเนื้อหาไฟล์ที่อัปโหลดทั้งหมดจะเข้าสู่บันทึก) นี่คือตัวอย่างรหัส แต่อย่าใช้:

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
สิ่งที่เกี่ยวกับร่างกายโพสต์?
Pavel Vyazankin

@dart ฉันได้เพิ่มตัวอย่างให้คุณ
Piotr Chowaniec

1
ฉันกำลังทำสิ่งนี้ แต่ปัญหาคือร่างกายการตอบสนองไม่พร้อมใช้งานTraceRepositoryเราจะเข้าถึงสิ่งนั้นได้อย่างไร
Amir Pashazadeh

@AmirPashazadeh คุณต้องแทนที่protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)แต่ฉันไม่แน่ใจว่าเมื่อใช้ตัวกรองนี้ - อาจอยู่ในช่วงคำขอดังนั้นเนื้อหาการตอบสนองจะไม่พร้อม
Piotr Chowaniec

1
@Kekar ​​ตั้งแต่ 2.0 มี HttpTraceRepository (แทน TraceRepository)
Piotr Chowaniec

12

รหัสนี้ใช้ได้กับฉันในแอปพลิเคชัน Spring Boot - เพียงลงทะเบียนเป็นตัวกรอง

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

        }
    }

วิธีนี้ใช้งานได้ดีสำหรับการบันทึกการตอบสนอง - แม้ว่าฉันจะต้อง จำกัด จำนวนไบต์ที่บันทึกไว้มิฉะนั้นจะทำให้การแสดงผลคอนโซลการบันทึก Intellij ล้มเหลว
อดัม

String getContent () {ถ้า (bos == null) {ส่งคืน String.format ("เรียกว่า% s เร็วเกินไป", BufferedResponseWrapper.class.getCanonicalName ()); } byte [] bytes = bos.toByteArray (); ส่งคืนสตริงใหม่ (Arrays.copyOf (ไบต์, 5,000)) + ".... "; }
Adam

นอกจากนี้ยังควรใส่สวิตช์ "log.isTraceEnabled ()" รอบ ๆ การบันทึกด้วย
Adam

6
สิ่งที่จะเจ๋งคือถ้า Java เพิ่มวิธีการเริ่มต้นบางอย่างใน HttpServletResponse ดังนั้นเราจึงไม่จำเป็นต้องเขียนการใช้งานขนาดใหญ่เช่นนี้
Adam

1
บวกหนึ่งสำหรับการรวมคำสั่งการนำเข้า
granadaCoder

7

นี่คือโซลูชันของฉัน (Spring 2.0.x)

เพิ่มการพึ่งพา maven:

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

แก้ไขแอปพลิเคชันคุณสมบัติและเพิ่มบรรทัดต่อไปนี้:

management.endpoints.web.exposure.include=* 

เมื่อเริ่มต้นแอปพลิเคชันเริ่มระบบฤดูใบไม้ผลิของคุณแล้วคุณสามารถติดตามคำขอ HTTP 100 รายการล่าสุดได้โดยเรียก URL นี้: http: // localhost: 8070 / actuator / httptrace


7

ปัจจุบัน Spring Boot มีคุณสมบัติ Actuator เพื่อรับบันทึกการร้องขอและการตอบกลับ

แต่คุณสามารถรับบันทึกได้โดยใช้ Aspect (AOP)

มุมมองให้คุณมีคำอธิบายประกอบที่ชอบ: @Before, @AfterReturning, @AfterThrowingฯลฯ

@Beforeบันทึกคำขอ@AfterReturningบันทึกการตอบสนองและ@AfterThrowingบันทึกข้อความแสดงข้อผิดพลาดคุณอาจไม่จำเป็นต้องใช้บันทึกปลายทางทั้งหมดดังนั้นคุณจึงสามารถใช้ตัวกรองบางอย่างกับแพ็คเกจได้

นี่คือตัวอย่าง :

สำหรับการร้องขอ:

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

ที่นี่@Before("within(your.package.where.endpoints.are..*)")มีเส้นทางแพคเกจ จุดสิ้นสุดทั้งหมดภายในแพ็คเกจนี้จะสร้างบันทึก

สำหรับการตอบกลับ:

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

ที่นี่@AfterReturning("within(your.package.where.endpoints.are..*)")มีเส้นทางแพคเกจ จุดสิ้นสุดทั้งหมดภายในแพ็คเกจนี้จะสร้างบันทึก ยังObject returnValueมีการตอบกลับ

สำหรับข้อยกเว้น:

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

ที่นี่@AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e") มีเส้นทางแพคเกจ จุดสิ้นสุดทั้งหมดภายในแพ็คเกจนี้จะสร้างบันทึก ยังException 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}")คุณสามารถเปิด / ปิดการใช้งานบันทึก เพียงเพิ่มendpoint.aspect.enabled:trueลงในapplication.propertyและควบคุมการบันทึก

ข้อมูลเพิ่มเติมเกี่ยวกับการเยี่ยมชม AOP ที่นี่:

ท่าเทียบเรือในฤดูใบไม้ผลิเกี่ยวกับ AOP

บทความตัวอย่างเกี่ยวกับ AOP


1
new ObjectMapper()มีราคาแพงดีกว่าแบ่งปัน mapper หนึ่งสำหรับทุกคน
Sam

ใช่แน่นอน. นี่คือรหัสตัวอย่าง ในการผลิตเราต้องปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุด
Md. Sajedul Karim

5

คุณยังสามารถกำหนดค่าตัวดักจับสปริงแบบกำหนดเอง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
    }
}

จากนั้นคุณลงทะเบียน interceptors มากเท่าที่คุณต้องการ:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    CustomHttpInterceptor customHttpInterceptor;

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

}

หมายเหตุ: เช่นเดียวกับที่ระบุโดย @Robertคุณต้องให้ความสนใจกับการใช้งานเฉพาะและแอปพลิเคชันของคุณกำลังใช้งานอยู่ HttpServletRequestHttpServletResponse

ตัวอย่างเช่นสำหรับแอปที่ใช้การShallowEtagHeaderFilterตอบสนองจะเป็น a ContentCachingResponseWrapperดังนั้นคุณจะต้อง:

@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

@ คำตอบของ 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);
    }
}

ใบสมัครของคุณบรรจุเป็นสงครามหรือไห ฉันได้รับข้อผิดพลาด java.io.FileNotFoundException: ไม่สามารถเปิดทรัพยากร ServletContext [/WEB-INF/loggingDispatcherServlet-servlet.xml]
Mayank Madhav

4

หากใครบางคนยังต้องการมันอยู่นี่คือการใช้งานอย่างง่ายกับ Spring HttpTrace Actuator แต่อย่างที่พวกเขาบอกไว้ข้างต้นว่ามันไม่ได้เป็นท่อนซุง

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

โปรดอ้างอิงลิงค์ด้านล่างสำหรับคำตอบจริง https://gist.github.com/int128/e47217bebdb4c402b2ffa7cc199307ba

ทำการเปลี่ยนแปลงบางอย่างจากวิธีการแก้ปัญหาที่แนะนำการร้องขอและการตอบสนองจะเข้าสู่ระบบคอนโซลและในไฟล์เช่นกันหากระดับตัวบันทึกเป็นข้อมูล เราสามารถพิมพ์ได้ทั้งในคอนโซลหรือไฟล์

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

1
คำตอบที่ดีข้อเสนอแนะเท่านั้นที่จะรวบรวมผลลัพธ์ทั้งหมดลงในบัฟเฟอร์และเข้าสู่ระบบในคำสั่งเดียว
Mike

2

หากคุณเห็นเพียงส่วนหนึ่งของเพย์โหลดคำขอของคุณคุณจะต้องเรียกใช้setMaxPayloadLengthฟังก์ชั่นตามค่าเริ่มต้นที่จะแสดงเพียง 50 อักขระในเนื้อหาคำขอของคุณ นอกจากนี้การตั้งค่าsetIncludeHeadersเป็นเท็จเป็นความคิดที่ดีถ้าคุณไม่ต้องการบันทึกส่วนหัวรับรองความถูกต้องของคุณ!

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

ฉันพยายามที่จะใช้มันในสปริง mvc และมันไม่ทำงานสำหรับฉันมันต้องมีการตั้งค่าเพิ่มเติมใด ๆ ยกเว้นการลงทะเบียน bean นี้และเพิ่มตัวบันทึก?
Noman Akhtar

1

ถ้าคุณใช้ Tomcat ในแอพสำหรับบูตที่นี่อยู่org.apache.catalina.filters.RequestDumperFilterในคลาสพา ธ สำหรับคุณ (แต่จะไม่ให้ "ข้อยกเว้นในที่เดียว" ให้คุณ)


1

รหัสที่วางไว้ด้านล่างใช้ได้กับการทดสอบของฉันและสามารถดาวน์โหลดได้จาก [โครงการ github] [1] ของฉันการแบ่งปันหลังจากใช้วิธีการแก้ปัญหาตามโครงการในการผลิต

@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

เพื่อที่จะบันทึกการร้องขอทั้งหมดที่มีป้อนพารามิเตอร์และร่างกายของเราสามารถใช้ฟิลเตอร์และขับไล่ แต่ในขณะที่ใช้ตัวกรองหรือตัวดักเราไม่สามารถพิมพ์เนื้อหาคำขอได้หลายครั้ง วิธีที่ดีกว่าคือเราสามารถใช้ spring-AOP ด้วยการใช้สิ่งนี้เราสามารถแยกกลไกการบันทึกจากแอปพลิเคชัน AOP สามารถใช้สำหรับบันทึกการเข้าและส่งออกของแต่ละวิธีในแอปพลิเคชัน

ทางออกของฉันคือ

 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

หากคุณมีการกำหนดค่าเซิร์ฟเวอร์การบูตของ Spring boot ให้เปิดใช้งาน Debug logger สำหรับคลาส

Http11InputBuffer.Http11InputBuffer.java

Debugs จะบันทึกการร้องขอและการตอบกลับทั้งหมดสำหรับทุกคำขอ


-1

ในการบันทึกคำขอที่ส่งผลให้ 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);
        }
    }

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.