Spring RestTemplate - làm thế nào để kích hoạt gỡ lỗi / ghi nhật ký yêu cầu / phản hồi đầy đủ?


220

Tôi đã sử dụng Spring RestTemplate trong một thời gian và tôi liên tục va vào tường khi tôi đang cố gắng gỡ lỗi các yêu cầu và phản hồi của nó. Về cơ bản, tôi đang tìm kiếm để xem những điều tương tự như tôi thấy khi tôi sử dụng curl với tùy chọn "verbose" được bật. Ví dụ :

curl -v http://twitter.com/statuses/public_timeline.rss

Sẽ hiển thị cả dữ liệu đã gửi và dữ liệu nhận được (bao gồm các tiêu đề, cookie, v.v.).

Tôi đã kiểm tra một số bài viết liên quan như: Làm cách nào để đăng nhập phản hồi trong Spring RestTemplate? nhưng tôi không thể giải quyết vấn đề này.

Một cách để làm điều này là thực sự thay đổi mã nguồn RestTemplate và thêm một số báo cáo ghi nhật ký bổ sung ở đó, nhưng tôi sẽ thấy cách tiếp cận này thực sự là một biện pháp cuối cùng. Cần có một số cách để nói với Spring Web Client / RestTemplate để ghi nhật ký mọi thứ theo cách thân thiện hơn nhiều.

Mục tiêu của tôi sẽ là có thể làm điều này với mã như:

restTemplate.put("http://someurl", objectToPut, urlPathValues);

và sau đó để có được cùng loại thông tin gỡ lỗi (như tôi nhận được với curl) trong tệp nhật ký hoặc trong bảng điều khiển. Tôi tin rằng điều này sẽ cực kỳ hữu ích cho bất kỳ ai sử dụng Spring RestTemplate và có vấn đề. Sử dụng curl để gỡ lỗi các vấn đề RestTemplate của bạn không hoạt động (trong một số trường hợp).


30
Cảnh báo cho bất cứ ai đọc năm 2018: Không có câu trả lời đơn giản cho vấn đề này!
davidfrancis

3
Cách dễ nhất là sử dụng một điểm dừng trong phương thức write (...) của lớp AbstractHttpMessageConverter, có một đối tượng outputMessage nơi bạn có thể xem dữ liệu. PS Bạn có thể sao chép giá trị và sau đó định dạng nó bằng định dạng trực tuyến.
Serge Chepurnov

1
Có vẻ như điều này sẽ dễ thực hiện trong Mùa xuân, nhưng, đánh giá từ các câu trả lời ở đây - không phải là trường hợp. Vì vậy, một giải pháp khác, sẽ là bỏ qua Spring hoàn toàn và sử dụng một công cụ như Fiddler để nắm bắt yêu cầu / phản hồi.
michaelok

đọc câu trả lời cho câu hỏi này từ một liên kết sau: spring-resttemplate-how-to-enable-full-debugging-log-of-request-
answer

Tháng 7 năm 2019: Vì vẫn chưa có giải pháp đơn giản cho câu hỏi này, tôi đã cố gắng đưa ra một bản tóm tắt của 24 câu trả lời khác (cho đến nay) và nhận xét và thảo luận của họ trong câu trả lời của riêng tôi dưới đây . Hy vọng nó giúp.
Chris

Câu trả lời:


206

Chỉ cần hoàn thành ví dụ với việc thực hiện đầy đủ ClientHttpRequestInterceptorđể theo dõi yêu cầu và phản hồi:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

    final static Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        traceResponse(response);
        return response;
    }

    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        log.info("===========================request begin================================================");
        log.debug("URI         : {}", request.getURI());
        log.debug("Method      : {}", request.getMethod());
        log.debug("Headers     : {}", request.getHeaders() );
        log.debug("Request body: {}", new String(body, "UTF-8"));
        log.info("==========================request end================================================");
    }

    private void traceResponse(ClientHttpResponse response) throws IOException {
        StringBuilder inputStringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8"));
        String line = bufferedReader.readLine();
        while (line != null) {
            inputStringBuilder.append(line);
            inputStringBuilder.append('\n');
            line = bufferedReader.readLine();
        }
        log.info("============================response begin==========================================");
        log.debug("Status code  : {}", response.getStatusCode());
        log.debug("Status text  : {}", response.getStatusText());
        log.debug("Headers      : {}", response.getHeaders());
        log.debug("Response body: {}", inputStringBuilder.toString());
        log.info("=======================response end=================================================");
    }

}

Sau đó khởi tạo RestTemplatebằng cách sử dụng a BufferingClientHttpRequestFactoryLoggingRequestInterceptor:

RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new LoggingRequestInterceptor());
restTemplate.setInterceptors(interceptors);

Điều BufferingClientHttpRequestFactorynày là bắt buộc vì chúng tôi muốn sử dụng phần thân phản hồi cả trong phần chặn và cho mã gọi ban đầu. Việc thực hiện mặc định chỉ cho phép đọc phần thân phản hồi một lần.


27
Cái này sai. Nếu bạn đọc luồng, mã ứng dụng sẽ không thể đọc phản hồi.
James Watkins

28
chúng tôi đã cung cấp cho RestTemplate một BufferingClientHttpRequestFactory để chúng tôi có thể đọc phản hồi hai lần.
sofiene zaghdoudi

16
Chúng tôi đã sử dụng kỹ thuật này khoảng 3 tháng nay. Nó chỉ hoạt động với RestTemplate được cấu hình với BufferingClientHttpResponseWrapperhàm ý @sofienezaghdoudi. Tuy nhiên, nó không hoạt động khi được sử dụng trong các thử nghiệm sử dụng khung mockServer của spring do MockRestServiceServer.createServer(restTemplate)ghi đè lên RequestFactory thành InterceptingClientHttpRequestFactory.
RubesMN

8
Kỹ thuật là tốt, thực hiện là sai. Trường hợp 404, answer.getBody () ném IOException -> bạn không bao giờ nhận được nhật ký và thậm chí tệ nhất, nó sẽ trở thành ResourceAccessException trong mã tiếp theo của bạn, thay vì RestClientResponseException
MilacH

5
Cảm ơn vi đa trả lơi. Nhưng đây là cách thực hành tồi để có nhiều "log.debug" vì nó có thể được trải rộng trên nhiều nhật ký khác. Tốt hơn là sử dụng một lệnh log.debug duy nhất để bạn chắc chắn mọi thứ đều ở cùng một nơi
user2447161

127

trong Spring Boot, bạn có thể nhận được toàn bộ yêu cầu / phản hồi bằng cách đặt điều này trong các thuộc tính (hoặc phương thức 12 yếu tố khác)

logging.level.org.apache.http=DEBUG

đầu ra này

-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827
-DEBUG o.a.http.impl.execchain.MainClientExec   : Executing request POST /v0/users HTTP/1.1
-DEBUG o.a.http.impl.execchain.MainClientExec   : Target auth state: UNCHALLENGED
-DEBUG o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> POST /v0/users HTTP/1.1
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Type: application/json;charset=UTF-8
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Length: 56
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Host: localhost:41827
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Accept-Encoding: gzip,deflate
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "POST /v0/users HTTP/1.1[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Type: application/json;charset=UTF-8[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Length: 56[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Host: localhost:41827[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "{"id":null,"email":"xenoterracide@gmail.com","new":true}"

và trả lời

-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827
-DEBUG o.a.http.impl.execchain.MainClientExec   : Executing request POST /v0/users HTTP/1.1
-DEBUG o.a.http.impl.execchain.MainClientExec   : Target auth state: UNCHALLENGED
-DEBUG o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> POST /v0/users HTTP/1.1
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Type: application/json;charset=UTF-8
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Length: 56
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Host: localhost:41827
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Accept-Encoding: gzip,deflate
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "POST /v0/users HTTP/1.1[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Type: application/json;charset=UTF-8[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Length: 56[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Host: localhost:41827[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "{"id":null,"email":"xenoterracide@gmail.com","new":true}"

hoặc chỉ logging.level.org.apache.http.wire=DEBUGcó vẻ chứa tất cả các thông tin liên quan


4
Đây là điều đơn giản nhất đã làm những gì tôi muốn. Tôi rất khuyến khích bao gồm điều này trong câu trả lời được chấp nhận.
michaelavila

22
Theo javadoc của RestTemplate :by default the RestTemplate relies on standard JDK facilities to establish HTTP connections. You can switch to use a different HTTP library such as Apache HttpComponents
Ortomala Lokni

22
RestTemplate không sử dụng các lớp Apache này theo mặc định, như được chỉ ra bởi @OrtomalaLokni, vì vậy bạn cũng nên bao gồm cách sử dụng chúng ngoài cách in gỡ lỗi khi chúng được sử dụng.
Thuyền trưởng Man

Tôi đang nhận được như thế này:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh

2
@ParthaSarathiGhosh Nội dung có thể được mã hóa bằng gzip, đó là lý do tại sao bạn không nhìn thấy văn bản thô.
Matthew Buckett

80

Mở rộng câu trả lời @hstoerr với một số mã:


Tạo LoggingRequestInterceptor để ghi lại các yêu cầu phản hồi

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

    private static final Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {

        ClientHttpResponse response = execution.execute(request, body);

        log(request,body,response);

        return response;
    }

    private void log(HttpRequest request, byte[] body, ClientHttpResponse response) throws IOException {
        //do logging
    }
}

Thiết lập RestTemplate

RestTemplate rt = new RestTemplate();

//set interceptors/requestFactory
ClientHttpRequestInterceptor ri = new LoggingRequestInterceptor();
List<ClientHttpRequestInterceptor> ris = new ArrayList<ClientHttpRequestInterceptor>();
ris.add(ri);
rt.setInterceptors(ris);
rt.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());

Điều này không có sẵn cho đến phiên bản mùa xuân-3.1.
Gyan

3
nó không trả lời câu hỏi 'phản hồi đăng nhập', nhưng để lại // làm bình luận ghi nhật ký thay thế.
Giang YD

1
thực hiện ghi nhật ký rất dễ dàng, nhưng điều này chỉ hoạt động đối với các yêu cầu, tôi không thấy các cơ quan phản hồi, giả sử tôi có đối tượng phản hồi, nhưng đọc luồng đó không phải là ý hay.
Pavel Niedoba

11
@PavelNiedoba BufferClientHttpRequestFactory cho phép phản hồi được đọc nhiều hơn một lần.
mjj1409

2
Điều này hoạt động tốt nếu bạn cần lưu trữ thông tin về yêu cầu / phản hồi trong cơ sở dữ liệu để gỡ lỗi và đăng nhập thường xuyên không phù hợp với nhu cầu của bạn.
GameSalutes

32

Đặt cược tốt nhất của bạn là thêm logging.level.org.springframework.web.client.RestTemplate=DEBUGvào application.propertiestập tin.

Các giải pháp khác như cài đặt log4j.logger.httpclient.wiresẽ không luôn hoạt động vì chúng cho rằng bạn sử dụng log4jvà Apache HttpClient, điều này không phải lúc nào cũng đúng.

Tuy nhiên, lưu ý rằng cú pháp này sẽ chỉ hoạt động trên các phiên bản mới nhất của Spring Boot.


5
Đây không phải là ghi nhật ký yêu cầu và nội dung phản hồi, chỉ là url và loại yêu cầu (spring-web-4.2.6)
vào

1
Bạn nói đúng, nó không phải là một bản wireghi, nó chỉ bao gồm các thông tin cần thiết như url, mã gửi lại, các tham số POST, v.v.
gamliela

1
những gì bạn thực sự muốn là stackoverflow.com/a/39109538/206466
xenoterracide 23/8/2016

Điều này là tốt nhưng cơ thể phản ứng không thể được nhìn thấy!
sunleo

Xuất sắc. Mặc dù nó không in phần thân phản hồi, nhưng nó vẫn rất hữu ích. Cảm ơn bạn.
Chris

30

Không có câu trả lời nào trong số này thực sự giải quyết được 100% vấn đề. mjj1409 nhận được hầu hết, nhưng thuận tiện tránh được vấn đề ghi nhật ký phản hồi, việc này sẽ tốn công hơn một chút. Paul Sabou cung cấp một giải pháp có vẻ thực tế, nhưng không cung cấp đủ chi tiết để thực hiện (và nó hoàn toàn không hiệu quả với tôi). Sofiene đã đăng nhập nhưng với một vấn đề nghiêm trọng: phản hồi không còn có thể đọc được vì luồng đầu vào đã được sử dụng!

Tôi khuyên bạn nên sử dụng BufferingClientHttpResponseWrapper để bọc đối tượng phản hồi để cho phép đọc nội dung phản hồi nhiều lần:

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LoggingRequestInterceptor.class);

    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        ClientHttpResponse response = execution.execute(request, body);

        response = log(request, body, response);

        return response;
    }

    private ClientHttpResponse log(final HttpRequest request, final byte[] body, final ClientHttpResponse response) {
        final ClientHttpResponse responseCopy = new BufferingClientHttpResponseWrapper(response);
        logger.debug("Method: ", request.getMethod().toString());
        logger.debug("URI: ", , request.getURI().toString());
        logger.debug("Request Body: " + new String(body));
        logger.debug("Response body: " + IOUtils.toString(responseCopy.getBody()));
        return responseCopy;
    }

}

Điều này sẽ không tiêu thụ InputStream vì phần thân phản hồi được tải vào bộ nhớ và có thể được đọc nhiều lần. Nếu bạn không có BufferingClientHttpResponseWrapper trên đường dẫn lớp của mình, bạn có thể tìm thấy cách thực hiện đơn giản tại đây:

https://github.com

Để thiết lập RestTemplate:

LoggingRequestInterceptor loggingInterceptor = new LoggingRequestInterceptor();
restTemplate.getInterceptors().add(loggingInterceptor);

tương tự, answerCopy.getBody () ném IOexception trong trường hợp 404, vì vậy bạn không bao giờ gửi lại cho mã của mình phản hồi nữa và RestClientResponseException thường trở thành ResourceAccessException
MilacH

1
Bạn nên kiểm tra status==200trướcresponseCopy.getBody()
Anand Rockzz

4
Nhưng đó là gói riêng tư. Bạn đã đặt LoggingRequestInterceptor của mình trong gói 'org.springframework.http.client' chưa?
zbstof

2
những gì về asyncRestTemplate? Nó sẽ yêu cầu trả lại một ListenableFuturekhi bạn chặn nó mà không thể thay đổi BufferingClientHttpResponseWrappertrong một cuộc gọi lại.
Ömer Faruk Almalı

@ MerFarukAlmalı Trong trường hợp đó, bạn sẽ cần sử dụng chuỗi hoặc biến đổi tùy thuộc vào phiên bản Guava bạn đang sử dụng. Xem: stackoverflow.com/questions/8191891/ khăn
James Watkins

29

Các giải pháp được đưa ra bởi xenoterracide để sử dụng

logging.level.org.apache.http=DEBUG

là tốt nhưng vấn đề là mặc định Apache HTTPComponents không được sử dụng.

Để sử dụng các HTTPComponents của Apache, hãy thêm vào tệp pom.xml của bạn

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpasyncclient</artifactId>
</dependency>

và cấu hình RestTemplatevới:

RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());

Cách dễ nhất, tôi sẽ chỉ thêm rằng nó không hoạt động với MockRestServiceServer, vì nó ghi đè requestFactory.
zbstof

Hoạt động tốt và không có vấn đề ít cấu hình!
sunleo

29

Bạn có thể sử dụng spring-rest-template-logger để ghi RestTemplatelưu lượng HTTP.

Thêm một phụ thuộc vào dự án Maven của bạn:

<dependency>
    <groupId>org.hobsoft.spring</groupId>
    <artifactId>spring-rest-template-logger</artifactId>
    <version>2.0.0</version>
</dependency>

Sau đó tùy chỉnh của bạn RestTemplatenhư sau:

RestTemplate restTemplate = new RestTemplateBuilder()
    .customizers(new LoggingCustomizer())
    .build()

Đảm bảo rằng ghi nhật ký gỡ lỗi được bật trong application.properties:

logging.level.org.hobsoft.spring.resttemplatelogger.LoggingCustomizer = DEBUG

Bây giờ tất cả lưu lượng truy cập HTTP RestTemplate sẽ được đăng nhập org.hobsoft.spring.resttemplatelogger.LoggingCustomizerở mức gỡ lỗi.

TUYÊN BỐ TỪ CHỐI: Tôi đã viết thư viện này.


Tại sao câu trả lời này bị bỏ qua? Nó đã giúp đỡ tôi. Cảm ơn, @Mark Hobson.
Raffael Bechara Rameh

3
Vui mừng vì nó đã giúp @RaffaelBecharaRameh. Ban đầu nó bị hạ cấp vì tôi không nhúng các hướng dẫn từ dự án được liên kết. Hãy upvote nếu bạn thấy nó hữu ích!
Mark Hobson

Bạn có hỗ trợ qua Gradle không?
BlackHatSamurai

1
@BlackHatSamurai spring-rest-template-logger là một tạo tác Maven thông thường, vì vậy nó sẽ hoạt động tốt với Gradle.
Mark Hobson

1
Xin chào @erhanasikoglu, bạn được chào đón! Đúng vậy, bạn có thể thấy nó được sử dụng ở đây: github.com/markhobson/spring-rest-template-logger/blob/master/ trộm
Mark Hobson

26

Cuối cùng tôi đã tìm thấy một cách để làm điều này theo cách đúng đắn. Hầu hết các giải pháp đến từ Làm cách nào để định cấu hình Spring và SLF4J để tôi có thể đăng nhập?

Dường như có hai điều cần phải làm:

  1. Thêm dòng sau vào log4j.properies: log4j.logger.httpclient.wire=DEBUG
  2. Hãy chắc chắn rằng mùa xuân không bỏ qua cấu hình đăng nhập của bạn

Vấn đề thứ hai xảy ra chủ yếu đối với môi trường mùa xuân nơi slf4j được sử dụng (như trường hợp của tôi). Như vậy, khi slf4j được sử dụng, hãy đảm bảo rằng hai điều sau đây xảy ra:

  1. Không có thư viện ghi nhật ký chung trong đường dẫn lớp của bạn: điều này có thể được thực hiện bằng cách thêm các mô tả loại trừ trong pom của bạn:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. Tệp log4j.properIES được lưu trữ ở đâu đó trong đường dẫn lớp nơi mùa xuân có thể tìm / nhìn thấy nó. Nếu bạn gặp vấn đề với điều này, một giải pháp cuối cùng sẽ là đặt tệp log4j.properIES vào gói mặc định (không phải là một cách thực hành tốt mà chỉ để thấy mọi thứ hoạt động như bạn mong đợi)


7
Điều này không làm việc cho tôi, tôi đã làm cả hai điều. Tôi không hiểu tại sao tôi cần đặt log4j.properies khi nó không được sử dụng trong dự án của tôi (được kiểm tra bởi phụ thuộc mvn: cây)
Pavel Niedoba

Điều này cũng không làm việc cho tôi. Tôi thậm chí đã thử thiết lập logger gốc thành chế độ Gỡ lỗi và vẫn không có gì.
James Watkins

"Httpclient.wire.content" và "httpclient.wire.header" là tên logger từ khung Axis2. Chúng có thể được sử dụng để ghi nhật ký, ví dụ như các yêu cầu SOAP trong dự án Spring nếu chúng được thực hiện bằng Axis2.
lathspell

11
httpclient.wirethực ra là từ thư viện HTTPComponents của Apache (xem hc.apache.org/httpcomponents-client-ga/logging.html ). Kỹ thuật này sẽ chỉ hoạt động nếu bạn đã RestTemplatecấu hình để sử dụngHttpComponentsClientHttpRequestFactory
Scott Frederick

20

Đăng nhập RestTemplate

Tùy chọn 1. Mở ghi nhật ký gỡ lỗi.

Cấu hình RestTemplate

  • Theo mặc định, RestTemplate dựa trên các cơ sở JDK tiêu chuẩn để thiết lập các kết nối HTTP. Bạn có thể chuyển sang sử dụng một thư viện HTTP khác, chẳng hạn như Apache HTTPComponents

    @Bean công khai RestTemplate restTemplate (Trình xây dựng RestTemplateBuilder) {RestTemplate restTemplate = builder.build (); trả lại phần còn lại; }

Cấu hình ghi nhật ký

  • application.yml

    ghi nhật ký: cấp độ: org.springframework.web.client.RestTemplate: DEBUG

Tùy chọn 2. Sử dụng Thiết bị đánh chặn

Phản ứng bao bọc

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StreamUtils;

public final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {

    private final ClientHttpResponse response;

    private byte[] body;


    BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
        this.response = response;
    }

    public HttpStatus getStatusCode() throws IOException {
        return this.response.getStatusCode();
    }

    public int getRawStatusCode() throws IOException {
        return this.response.getRawStatusCode();
    }

    public String getStatusText() throws IOException {
        return this.response.getStatusText();
    }

    public HttpHeaders getHeaders() {
        return this.response.getHeaders();
    }

    public InputStream getBody() throws IOException {
        if (this.body == null) {
            this.body = StreamUtils.copyToByteArray(this.response.getBody());
        }
        return new ByteArrayInputStream(this.body);
    }

    public void close() {
        this.response.close();
    }
}

Thực hiện đánh chặn

package com.example.logging;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

public class LoggingRestTemplate implements ClientHttpRequestInterceptor {

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

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body,
            ClientHttpRequestExecution execution) throws IOException {
        traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        return traceResponse(response);
    }

    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        if (!LOGGER.isDebugEnabled()) {
            return;
        }
        LOGGER.debug(
                "==========================request begin==============================================");
        LOGGER.debug("URI                 : {}", request.getURI());
        LOGGER.debug("Method            : {}", request.getMethod());
        LOGGER.debug("Headers         : {}", request.getHeaders());
        LOGGER.debug("Request body: {}", new String(body, "UTF-8"));
        LOGGER.debug(
                "==========================request end================================================");
    }

    private ClientHttpResponse traceResponse(ClientHttpResponse response) throws IOException {
        if (!LOGGER.isDebugEnabled()) {
            return response;
        }
        final ClientHttpResponse responseWrapper = new BufferingClientHttpResponseWrapper(response);
        StringBuilder inputStringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(responseWrapper.getBody(), "UTF-8"));
        String line = bufferedReader.readLine();
        while (line != null) {
            inputStringBuilder.append(line);
            inputStringBuilder.append('\n');
            line = bufferedReader.readLine();
        }
        LOGGER.debug(
                "==========================response begin=============================================");
        LOGGER.debug("Status code    : {}", responseWrapper.getStatusCode());
        LOGGER.debug("Status text    : {}", responseWrapper.getStatusText());
        LOGGER.debug("Headers            : {}", responseWrapper.getHeaders());
        LOGGER.debug("Response body: {}", inputStringBuilder.toString());
        LOGGER.debug(
                "==========================response end===============================================");
        return responseWrapper;
    }

}

Cấu hình RestTemplate

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    RestTemplate restTemplate = builder.build();
    restTemplate.setInterceptors(Collections.singletonList(new LoggingRestTemplate()));
    return restTemplate;
}

Cấu hình ghi nhật ký

  • Kiểm tra gói LoggingRestTemplate, ví dụ như trong application.yml:

    đăng nhập: cấp độ: com.example.logging: DEBUG

Tùy chọn 3. Sử dụng httpcomponent

Nhập phụ thuộc httpcomponent

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpasyncclient</artifactId>

Cấu hình RestTemplate

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    RestTemplate restTemplate = builder.build();
    restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());
    return restTemplate;
}

Cấu hình ghi nhật ký

  • Kiểm tra gói LoggingRestTemplate, ví dụ như trong application.yml:

    đăng nhập: cấp độ: org.apache.http: DEBUG


Chỉ cần lưu ý: nếu bạn muốn định cấu hình TestRestTemplate, hãy định cấu hình RestTemplateBuilder: @Bean công khai RestTemplateBuilder restTemplateBuilder () {trả về RestTemplateBuilder mới (). }
kingoleg

Cũng lưu ý rằng InputStreamReader mới (answerWrapper.getBody (), StandardCharsets.UTF_8)); có thể đưa ra lỗi nếu "đầu kia" trả về lỗi. Bạn có thể muốn đặt nó vào một khối thử.
PeterS

15

---- Tháng 7 năm 2019 ----

(sử dụng Spring Boot)

Tôi đã rất ngạc nhiên khi Spring Boot, với tất cả là phép thuật Cấu hình Zero, không cung cấp một cách dễ dàng để kiểm tra hoặc ghi nhật ký phần thân phản hồi JSON đơn giản với RestTemplate. Tôi đã xem qua các câu trả lời và nhận xét khác nhau được cung cấp ở đây và đang chia sẻ phiên bản chưng cất của riêng tôi về những gì (vẫn) hoạt động và dường như tôi thích một giải pháp hợp lý, đưa ra các tùy chọn hiện tại (Tôi đang sử dụng Spring Boot 2.1.6 với Gradle 4.4 )

1. Sử dụng Fiddler làm http proxy

Đây thực sự là một giải pháp tao nhã, vì nó bỏ qua tất cả những nỗ lực rườm rà trong việc tạo ra thiết bị đánh chặn của riêng bạn hoặc thay đổi ứng dụng khách http cơ bản thành apache (xem bên dưới).

Cài đặt và chạy Fiddler

và sau đó

thêm -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888vào tùy chọn VM của bạn

2. Sử dụng Apache httpClient

Thêm Apache httpClient vào các phụ thuộc Maven hoặc Gradle của bạn.

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.9</version>
</dependency>

Sử dụng HttpComponentsClientHttpRequestFactorynhư RequestFactory cho RestTemplate. Cách đơn giản nhất để làm điều đó là:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Bật DEBUG trong application.propertiestệp của bạn (nếu bạn đang sử dụng Spring Boot)

logging.level.org.apache.http=DEBUG

Nếu bạn đang sử dụng Spring Boot, bạn sẽ cần đảm bảo rằng bạn đã thiết lập khung đăng nhập, ví dụ: bằng cách sử dụng phụ thuộc khởi động lò xo khởi động bao gồm spring-boot-starter-logging.

3. Sử dụng một Thiết bị đánh chặn

Tôi sẽ cho phép bạn đọc qua các đề xuất, phản hồi đề xuất và gotchas trong các câu trả lời và nhận xét khác và tự quyết định nếu bạn muốn đi theo con đường đó.

4. Đăng nhập URL và Trạng thái phản hồi không có nội dung

Mặc dù điều này không đáp ứng các yêu cầu đã nêu của việc ghi nhật ký nội dung, nhưng đây là cách nhanh chóng và đơn giản để bắt đầu ghi nhật ký các cuộc gọi REST của bạn. Nó hiển thị URL đầy đủ và trạng thái phản hồi.

Chỉ cần thêm dòng sau vào application.propertiestệp của bạn (giả sử bạn đang sử dụng Spring Boot và giả sử bạn đang sử dụng phụ thuộc khởi động lò xo bao gồm spring-boot-starter-logging)

log.level.org.springframework.web.client.RestTemplate = DEBUG

Đầu ra sẽ trông giống như thế này:

2019-07-29 11:53:50.265 DEBUG o.s.web.client.RestTemplate : HTTP GET http://www.myrestservice.com/Endpoint?myQueryParam=myValue
2019-07-29 11:53:50.276 DEBUG o.s.web.client.RestTemplate : Accept=[application/json]
2019-07-29 11:53:50.584 DEBUG o.s.web.client.RestTemplate : Response 200 OK
2019-07-29 11:53:50.585 DEBUG o.s.web.client.RestTemplate : Reading to [org.mynamespace.MyJsonModelClass]

2
Số 4 là cách dễ nhất để gỡ lỗi.
Yubaraj

1
Số 2 làm việc cho tôi. Nó ghi nhật ký yêu cầu. Cảm ơn bạn!
caglar

1
Tôi tìm thấy số 3 một cách dễ dàng để làm điều này khi tôi gặp vấn đề này.
Bill Naylor

12

Bên cạnh việc ghi nhật ký httpClient được mô tả trong câu trả lời khác , bạn cũng có thể giới thiệu một ClientHttpRequestInterceptor đọc phần thân của yêu cầu và phản hồi và ghi lại nó. Bạn có thể muốn làm điều này nếu các công cụ khác cũng sử dụng HTTPClient hoặc nếu bạn muốn định dạng ghi nhật ký tùy chỉnh. Thận trọng: bạn sẽ muốn cung cấp cho RestTemplate một BufferingClientHttpRequestFactory để bạn có thể đọc phản hồi hai lần.


12

Như đã nêu trong các phản hồi khác, cơ thể phản hồi cần được xử lý đặc biệt để có thể đọc lại nhiều lần (theo mặc định, nội dung của nó được tiêu thụ trong lần đọc đầu tiên).

Thay vì sử dụng BufferingClientHttpRequestFactorykhi thiết lập yêu cầu, chính bộ chặn có thể bao bọc phản hồi và đảm bảo nội dung được giữ lại và có thể được đọc lại nhiều lần (bởi người ghi chép cũng như người tiêu dùng phản hồi):

Đánh chặn của tôi, mà

  • đệm cơ thể phản hồi bằng cách sử dụng một trình bao bọc
  • đăng nhập một cách nhỏ gọn hơn
  • cũng ghi nhật ký nhận dạng mã trạng thái (ví dụ: 201 Tạo)
  • bao gồm số thứ tự yêu cầu cho phép dễ dàng phân biệt các mục nhật ký đồng thời với nhiều luồng

Mã số:

public class LoggingInterceptor implements ClientHttpRequestInterceptor {

    private final Logger log = LoggerFactory.getLogger(getClass());
    private AtomicInteger requestNumberSequence = new AtomicInteger(0);

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        int requestNumber = requestNumberSequence.incrementAndGet();
        logRequest(requestNumber, request, body);
        ClientHttpResponse response = execution.execute(request, body);
        response = new BufferedClientHttpResponse(response);
        logResponse(requestNumber, response);
        return response;
    }

    private void logRequest(int requestNumber, HttpRequest request, byte[] body) {
        if (log.isDebugEnabled()) {
            String prefix = requestNumber + " > ";
            log.debug("{} Request: {} {}", prefix, request.getMethod(), request.getURI());
            log.debug("{} Headers: {}", prefix, request.getHeaders());
            if (body.length > 0) {
                log.debug("{} Body: \n{}", prefix, new String(body, StandardCharsets.UTF_8));
            }
        }
    }

    private void logResponse(int requestNumber, ClientHttpResponse response) throws IOException {
        if (log.isDebugEnabled()) {
            String prefix = requestNumber + " < ";
            log.debug("{} Response: {} {} {}", prefix, response.getStatusCode(), response.getStatusCode().name(), response.getStatusText());
            log.debug("{} Headers: {}", prefix, response.getHeaders());
            String body = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
            if (body.length() > 0) {
                log.debug("{} Body: \n{}", prefix, body);
            }
        }
    }

    /**
     * Wrapper around ClientHttpResponse, buffers the body so it can be read repeatedly (for logging & consuming the result).
     */
    private static class BufferedClientHttpResponse implements ClientHttpResponse {

        private final ClientHttpResponse response;
        private byte[] body;

        public BufferedClientHttpResponse(ClientHttpResponse response) {
            this.response = response;
        }

        @Override
        public HttpStatus getStatusCode() throws IOException {
            return response.getStatusCode();
        }

        @Override
        public int getRawStatusCode() throws IOException {
            return response.getRawStatusCode();
        }

        @Override
        public String getStatusText() throws IOException {
            return response.getStatusText();
        }

        @Override
        public void close() {
            response.close();
        }

        @Override
        public InputStream getBody() throws IOException {
            if (body == null) {
                body = StreamUtils.copyToByteArray(response.getBody());
            }
            return new ByteArrayInputStream(body);
        }

        @Override
        public HttpHeaders getHeaders() {
            return response.getHeaders();
        }
    }
}

Cấu hình:

 @Bean
    public RestTemplateBuilder restTemplateBuilder() {
        return new RestTemplateBuilder()
                .additionalInterceptors(Collections.singletonList(new LoggingInterceptor()));
    }

Ví dụ đầu ra nhật ký:

2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Request: POST http://localhost:53969/payment/v4/private/payment-lists/10022/templates
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Headers: {Accept=[application/json, application/json], Content-Type=[application/json;charset=UTF-8], Content-Length=[986]}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Body: 
{"idKey":null, ...}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Response: 200 OK 
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Headers: {Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Mon, 08 Oct 2018 08:58:53 GMT]}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Body: 
{ "idKey" : "10022", ...  }

1
Phiên bản này hoạt động với phiên bản Mùa xuân 2019 giữ cho cơ thể nguyên vẹn.
Udo tổ chức

1
Hoạt động vào mùa xuân 2.1.10 :) Cảm ơn
Moler

8

ứng dụng

logging.level.org.springframework.web.client=DEBUG

application.yml

logging:
  level:  
    root: WARN
    org.springframework.web.client: DEBUG

8

Đây có thể không phải là cách chính xác để làm điều đó, nhưng tôi nghĩ rằng đây là cách tiếp cận đơn giản nhất để in các yêu cầu và phản hồi mà không cần điền quá nhiều vào nhật ký.

Bằng cách thêm vào dưới 2 dòng application.properies ghi lại tất cả các yêu cầu và phản hồi dòng thứ 1 để ghi nhật ký các yêu cầu và dòng thứ 2 để ghi lại các phản hồi.

logging.level.org.springframework.web.client.RestTemplate=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor=DEBUG

Đăng nhập phản hồi không làm việc cho tôi. Nó chỉ đăng nhập Statuscode. Có nên đăng nhập tải trọng?
badera

Lớp HttpEntityMethodProcessor (v5.1.8) không ghi nhật ký gì cả.
Chris

6

Giả sử RestTemplateđược cấu hình để sử dụng HttpClient 4.x, bạn có thể đọc tài liệu đăng nhập của httpClient tại đây . Các loggers khác với những người được chỉ định trong các câu trả lời khác.

Cấu hình ghi nhật ký cho HttpClient 3.x có sẵn ở đây .


4

Vì vậy, nhiều phản hồi ở đây yêu cầu thay đổi mã hóa và các lớp tùy chỉnh và nó thực sự không cần thiết. Gte một proxy gỡ lỗi như fiddler và đặt môi trường java của bạn để sử dụng proxy trên dòng lệnh (-Dhttp.proxyhost và -Dhttp.proxyPort) sau đó chạy fiddler và bạn có thể thấy toàn bộ các yêu cầu và phản hồi. Cũng đi kèm với nhiều lợi thế phụ trợ như khả năng sửa đổi kết quả và phản hồi trước và sau khi chúng được gửi để chạy thử nghiệm trước khi cam kết sửa đổi máy chủ.

Vấn đề cuối cùng có thể xảy ra là nếu bạn phải sử dụng HTTPS, bạn sẽ cần xuất chứng chỉ SSL từ fiddler và nhập nó vào kho khóa java (mật khẩu): mật khẩu kho khóa java mặc định thường là "thay đổi".


1
Điều này làm việc cho tôi bằng cách sử dụng intellij và cài đặt thường xuyên của fiddle. Tôi đã chỉnh sửa Cấu hình Run và đặt tùy chọn VM thành -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD

Cảm ơn! Đây là một giải pháp khá thanh lịch so với việc viết Interceptor của riêng bạn.
Chris

3

Để đăng nhập vào Logback với sự trợ giúp từ Apache HttpClient:

Bạn cần Apache httpClient trong classpath:

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
  <version>4.5.10</version>
</dependency>

Định cấu hình RestTemplateđể sử dụng HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Để ghi nhật ký yêu cầu và phản hồi, hãy thêm vào tệp cấu hình Logback:

<logger name="org.apache.http.wire" level="DEBUG"/>

Hoặc để đăng nhập nhiều hơn:

<logger name="org.apache.http" level="DEBUG"/>

Tập tin cấu hình logback nào?
G_V

1
@G_V logback.xml hoặc logback-test.xml để kiểm tra.
holmis83

Nó cũng hoạt động với org.apache.http.wire=DEBUGtrong application.propertieshiện tại của bạn
G_V

@G_V nếu bạn đang sử dụng Spring-Boot. Câu trả lời của tôi hoạt động mà không cần Boot.
holmis83

2

Thủ thuật cấu hình của bạn RestTemplatevới một BufferingClientHttpRequestFactorycông cụ không hoạt động nếu bạn đang sử dụng bất kỳ ClientHttpRequestInterceptor, bạn sẽ làm điều đó nếu bạn đang cố gắng đăng nhập thông qua các thiết bị chặn. Điều này là do cách mà InterceptingHttpAccessor( RestTemplatelớp con) hoạt động.

Câu chuyện dài ... chỉ cần sử dụng lớp này thay cho RestTemplate(lưu ý điều này sử dụng API ghi nhật ký SLF4J, chỉnh sửa khi cần):

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;

/**
 * A {@link RestTemplate} that logs every request and response.
 */
public class LoggingRestTemplate extends RestTemplate {

    // Bleh, this class is not public
    private static final String RESPONSE_WRAPPER_CLASS = "org.springframework.http.client.BufferingClientHttpResponseWrapper";

    private Logger log = LoggerFactory.getLogger(this.getClass());

    private boolean hideAuthorizationHeaders = true;
    private Class<?> wrapperClass;
    private Constructor<?> wrapperConstructor;

    /**
     * Configure the logger to log requests and responses to.
     *
     * @param log log destination, or null to disable
     */
    public void setLogger(Logger log) {
        this.log = log;
    }

    /**
     * Configure the logger to log requests and responses to by name.
     *
     * @param name name of the log destination, or null to disable
     */
    public void setLoggerName(String name) {
        this.setLogger(name != null ? LoggerFactory.getLogger(name) : null);
    }

    /**
     * Configure whether to hide the contents of {@code Authorization} headers.
     *
     * <p>
     * Default true.
     *
     * @param hideAuthorizationHeaders true to hide, otherwise false
     */
    public void setHideAuthorizationHeaders(boolean hideAuthorizationHeaders) {
        this.hideAuthorizationHeaders = hideAuthorizationHeaders;
    }

    /**
     * Log a request.
     */
    protected void traceRequest(HttpRequest request, byte[] body) {
        this.log.debug("xmit: {} {}\n{}{}", request.getMethod(), request.getURI(), this.toString(request.getHeaders()),
          body != null && body.length > 0 ? "\n\n" + new String(body, StandardCharsets.UTF_8) : "");
    }

    /**
     * Log a response.
     */
    protected void traceResponse(ClientHttpResponse response) {
        final ByteArrayOutputStream bodyBuf = new ByteArrayOutputStream();
        HttpStatus statusCode = null;
        try {
            statusCode = response.getStatusCode();
        } catch (IOException e) {
            // ignore
        }
        String statusText = null;
        try {
            statusText = response.getStatusText();
        } catch (IOException e) {
            // ignore
        }
        try (final InputStream input = response.getBody()) {
            byte[] b = new byte[1024];
            int r;
            while ((r = input.read(b)) != -1)
                bodyBuf.write(b, 0, r);
        } catch (IOException e) {
            // ignore
        }
        this.log.debug("recv: {} {}\n{}{}", statusCode, statusText, this.toString(response.getHeaders()),
          bodyBuf.size() > 0 ? "\n\n" + new String(bodyBuf.toByteArray(), StandardCharsets.UTF_8) : "");
    }

    @PostConstruct
    private void addLoggingInterceptor() {
        this.getInterceptors().add(new ClientHttpRequestInterceptor() {
            @Override
            public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
              throws IOException {

                // Log request
                if (LoggingRestTemplate.this.log != null && LoggingRestTemplate.this.log.isDebugEnabled())
                    LoggingRestTemplate.this.traceRequest(request, body);

                // Perform request
                ClientHttpResponse response = execution.execute(request, body);

                // Log response
                if (LoggingRestTemplate.this.log != null && LoggingRestTemplate.this.log.isDebugEnabled()) {
                    final ClientHttpResponse bufferedResponse = LoggingRestTemplate.this.ensureBuffered(response);
                    if (bufferedResponse != null) {
                        LoggingRestTemplate.this.traceResponse(bufferedResponse);
                        response = bufferedResponse;
                    }
                }

                // Done
                return response;
            }
        });
    }

    private ClientHttpResponse ensureBuffered(ClientHttpResponse response) {
        try {
            if (this.wrapperClass == null)
                this.wrapperClass = Class.forName(RESPONSE_WRAPPER_CLASS, false, ClientHttpResponse.class.getClassLoader());
            if (!this.wrapperClass.isInstance(response)) {
                if (this.wrapperConstructor == null) {
                    this.wrapperConstructor = this.wrapperClass.getDeclaredConstructor(ClientHttpResponse.class);
                    this.wrapperConstructor.setAccessible(true);
                }
                response = (ClientHttpResponse)this.wrapperConstructor.newInstance(response);
            }
            return response;
        } catch (Exception e) {
            this.log.error("error creating {} instance: {}", RESPONSE_WRAPPER_CLASS, e);
            return null;
        }
    }

    private String toString(HttpHeaders headers) {
        final StringBuilder headerBuf = new StringBuilder();
        for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
            if (headerBuf.length() > 0)
                headerBuf.append('\n');
            final String name = entry.getKey();
            for (String value : entry.getValue()) {
                if (this.hideAuthorizationHeaders && name.equalsIgnoreCase(HttpHeaders.AUTHORIZATION))
                    value = "[omitted]";
                headerBuf.append(name).append(": ").append(value);
            }
        }
        return headerBuf.toString();
    }
}

Tôi đồng ý rằng thật ngớ ngẩn khi phải mất nhiều công sức chỉ để làm điều này.


2

Thêm vào thảo luận ở trên điều này chỉ đại diện cho các kịch bản hạnh phúc. có lẽ bạn sẽ không thể đăng nhập phản hồi nếu có Lỗi .

Trong trường hợp này cộng với tất cả các trường hợp trên, bạn phải ghi đè DefaultResponseErrorHandler và đặt nó như bên dưới

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());

2

Thật kỳ lạ, không có giải pháp nào trong số các giải pháp này hoạt động vì RestTemplate dường như không trả về phản hồi trên một số lỗi máy khách và máy chủ 500x. Trong trường hợp đó, bạn cũng sẽ đăng nhập những cái đó bằng cách triển khai FeedbackErrorHandler như sau. Đây là một mã dự thảo, nhưng bạn nhận được điểm:

Bạn có thể đặt bộ chặn tương tự như trình xử lý lỗi:

restTemplate.getInterceptors().add(interceptor);
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
restTemplate.setErrorHandler(interceptor);

Và việc chặn thực hiện cả hai giao diện:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.ResponseErrorHandler;

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor, ResponseErrorHandler {
    static final Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
    static final DefaultResponseErrorHandler defaultResponseErrorHandler = new DefaultResponseErrorHandler();
    final Set<Series> loggableStatuses = new HashSet();

    public LoggingRequestInterceptor() {
    }

    public LoggingRequestInterceptor(Set<Series> loggableStatuses) {
        loggableStatuses.addAll(loggableStatuses);
    }

    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        this.traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        if(response != null) {
            this.traceResponse(response);
        }

        return response;
    }

    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        log.debug("===========================request begin================================================");
        log.debug("URI         : {}", request.getURI());
        log.debug("Method      : {}", request.getMethod());
        log.debug("Headers     : {}", request.getHeaders());
        log.debug("Request body: {}", new String(body, "UTF-8"));
        log.debug("==========================request end================================================");
    }

    private void traceResponse(ClientHttpResponse response) throws IOException {
        if(this.loggableStatuses.isEmpty() || this.loggableStatuses.contains(response.getStatusCode().series())) {
            StringBuilder inputStringBuilder = new StringBuilder();

            try {
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8"));

                for(String line = bufferedReader.readLine(); line != null; line = bufferedReader.readLine()) {
                    inputStringBuilder.append(line);
                    inputStringBuilder.append('\n');
                }
            } catch (Throwable var5) {
                log.error("cannot read response due to error", var5);
            }

            log.debug("============================response begin==========================================");
            log.debug("Status code  : {}", response.getStatusCode());
            log.debug("Status text  : {}", response.getStatusText());
            log.debug("Headers      : {}", response.getHeaders());
            log.debug("Response body: {}", inputStringBuilder.toString());
            log.debug("=======================response end=================================================");
        }

    }

    public boolean hasError(ClientHttpResponse response) throws IOException {
        return defaultResponseErrorHandler.hasError(response);
    }

    public void handleError(ClientHttpResponse response) throws IOException {
        this.traceResponse(response);
        defaultResponseErrorHandler.handleError(response);
    }
}

Điều gì xảy ra nếu phần thân là nhiều dữ liệu / biểu mẫu, có cách nào dễ dàng để lọc dữ liệu nhị phân (nội dung tệp) khỏi nhật ký không?
Lu-ca

1

Như @MilacH đã chỉ ra, có một lỗi trong quá trình thực hiện. Nếu một statusCode> 400 được trả về, IOException sẽ bị ném, vì errorHandler không được gọi, từ các bộ chặn. Ngoại lệ có thể được bỏ qua và sau đó được bắt lại trong phương thức xử lý.

package net.sprd.fulfillment.common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import static java.nio.charset.StandardCharsets.UTF_8;

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

    final static Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);

    @SuppressWarnings("HardcodedLineSeparator")
    public static final char LINE_BREAK = '\n';

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        try {
            traceRequest(request, body);
        } catch (Exception e) {
            log.warn("Exception in LoggingRequestInterceptor while tracing request", e);
        }

        ClientHttpResponse response = execution.execute(request, body);

        try {
            traceResponse(response);
        } catch (IOException e) {
            // ignore the exception here, as it will be handled by the error handler of the restTemplate
            log.warn("Exception in LoggingRequestInterceptor", e);
        }
        return response;
    }

    private void traceRequest(HttpRequest request, byte[] body) {
        log.info("===========================request begin================================================");
        log.info("URI         : {}", request.getURI());
        log.info("Method      : {}", request.getMethod());
        log.info("Headers     : {}", request.getHeaders());
        log.info("Request body: {}", new String(body, UTF_8));
        log.info("==========================request end================================================");
    }

    private void traceResponse(ClientHttpResponse response) throws IOException {
        StringBuilder inputStringBuilder = new StringBuilder();
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), UTF_8))) {
            String line = bufferedReader.readLine();
            while (line != null) {
                inputStringBuilder.append(line);
                inputStringBuilder.append(LINE_BREAK);
                line = bufferedReader.readLine();
            }
        }

        log.info("============================response begin==========================================");
        log.info("Status code  : {}", response.getStatusCode());
        log.info("Status text  : {}", response.getStatusText());
        log.info("Headers      : {}", response.getHeaders());
        log.info("Response body: {}", inputStringBuilder);
        log.info("=======================response end=================================================");
    }

}

0

Giải pháp tốt nhất bây giờ, chỉ cần thêm phụ thuộc:

<dependency>
  <groupId>com.github.zg2pro</groupId>
  <artifactId>spring-rest-basis</artifactId>
  <version>v.x</version>
</dependency>

Nó chứa một lớp LoggingRequestInterceptor mà bạn có thể thêm theo cách đó vào RestTemplate của mình:

tích hợp tiện ích này bằng cách thêm nó làm thiết bị đánh chặn vào RestTemplate mùa xuân, theo cách sau:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

và thêm một triển khai slf4j vào khung của bạn như log4j.

hoặc trực tiếp sử dụng "Zg2proRestTemplate" . "Câu trả lời hay nhất" của @PaulSabou trông có vẻ như vậy, vì httpclient và tất cả apache.http libs không nhất thiết phải được tải khi sử dụng RestTemplate mùa xuân.


phiên bản phát hành là gì?
popalka

phiên bản phát hành bây giờ là 0,2
Moses Meyer

1
dễ sử dụng là tuyệt vời, nhưng nó thiếu tiêu đề
WrRaThY

ngoài ra: tất cả các phương thức hữu ích trong LoggingRequestInterceptor là riêng tư, đây là một vấn đề khi nói đến phần mở rộng (có thể được bảo vệ)
WrRaThY

thật đáng buồn, tôi không thể chỉnh sửa bình luận sau 5 phút. Tất cả bạn phải làm để đăng nhập tiêu đề là thế này: log("Headers: {}", request.headers)trong LoggingRequestInterceptor:traceRequestlog("Headers: {}", response.headers)trong LoggingRequestInterceptor:logResponse. Bạn có thể muốn nghĩ về việc thêm một số cờ cho tiêu đề và nội dung. Ngoài ra - bạn có thể muốn kiểm tra loại nội dung cơ thể để ghi nhật ký (ví dụ: chỉ đăng nhập ứng dụng / json *). Điều này cũng nên được cấu hình. tất cả trong tất cả, với những điều chỉnh nhỏ đó, bạn sẽ có một thư viện đẹp để lan rộng. làm tốt lắm :)
WrRaThY

0

Muốn thêm tôi thực hiện điều này là tốt. Tôi xin lỗi vì tất cả các dấu chấm phẩy bị thiếu, điều này được viết bằng Groovy.

Tôi cần một cái gì đó cấu hình nhiều hơn câu trả lời được chấp nhận được cung cấp. Đây là một mẫu đậu còn lại rất nhanh nhẹn và sẽ ghi lại mọi thứ như OP đang tìm kiếm.

Lớp đánh chặn ghi nhật ký tùy chỉnh:

import org.springframework.http.HttpRequest
import org.springframework.http.client.ClientHttpRequestExecution
import org.springframework.http.client.ClientHttpRequestInterceptor
import org.springframework.http.client.ClientHttpResponse
import org.springframework.util.StreamUtils

import java.nio.charset.Charset

class HttpLoggingInterceptor implements ClientHttpRequestInterceptor {

    private final static Logger log = LoggerFactory.getLogger(HttpLoggingInterceptor.class)

    @Override
    ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        logRequest(request, body)
        ClientHttpResponse response = execution.execute(request, body)
        logResponse(response)
        return response
    }

    private void logRequest(HttpRequest request, byte[] body) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("===========================request begin================================================")
            log.debug("URI         : {}", request.getURI())
            log.debug("Method      : {}", request.getMethod())
            log.debug("Headers     : {}", request.getHeaders())
            log.debug("Request body: {}", new String(body, "UTF-8"))
            log.debug("==========================request end================================================")
        }
    }

    private void logResponse(ClientHttpResponse response) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("============================response begin==========================================")
            log.debug("Status code  : {}", response.getStatusCode())
            log.debug("Status text  : {}", response.getStatusText())
            log.debug("Headers      : {}", response.getHeaders())
            log.debug("Response body: {}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()))
            log.debug("=======================response end=================================================")
        }
    }
}

Phần còn lại Định nghĩa Bean Bean:

@Bean(name = 'myRestTemplate')
RestTemplate myRestTemplate(RestTemplateBuilder builder) {

    RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(10 * 1000) // 10 seconds
            .setSocketTimeout(300 * 1000) // 300 seconds
            .build()

    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager()
    connectionManager.setMaxTotal(10)
    connectionManager.closeIdleConnections(5, TimeUnit.MINUTES)

    CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .setDefaultRequestConfig(requestConfig)
            .disableRedirectHandling()
            .build()

    RestTemplate restTemplate = builder
            .rootUri("https://domain.server.com")
            .basicAuthorization("username", "password")
            .requestFactory(new BufferingClientHttpRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient)))
            .interceptors(new HttpLoggingInterceptor())
            .build()

    return restTemplate
}

Thực hiện:

@Component
class RestService {

    private final RestTemplate restTemplate
    private final static Logger log = LoggerFactory.getLogger(RestService.class)

    @Autowired
    RestService(
            @Qualifier("myRestTemplate") RestTemplate restTemplate
    ) {
        this.restTemplate = restTemplate
    }

    // add specific methods to your service that access the GET and PUT methods

    private <T> T getForObject(String path, Class<T> object, Map<String, ?> params = [:]) {
        try {
            return restTemplate.getForObject(path, object, params)
        } catch (HttpClientErrorException e) {
            log.warn("Client Error (${path}): ${e.responseBodyAsString}")
        } catch (HttpServerErrorException e) {
            String msg = "Server Error (${path}): ${e.responseBodyAsString}"
            log.error(msg, e)
        } catch (RestClientException e) {
            String msg = "Error (${path})"
            log.error(msg, e)
        }
        return null
    }

    private <T> T putForObject(String path, T object) {
        try {
            HttpEntity<T> request = new HttpEntity<>(object)
            HttpEntity<T> response = restTemplate.exchange(path, HttpMethod.PUT, request, T)
            return response.getBody()
        } catch (HttpClientErrorException e) {
            log.warn("Error (${path}): ${e.responseBodyAsString}")
        } catch (HttpServerErrorException e) {
            String msg = "Error (${path}): ${e.responseBodyAsString}"
            log.error(msg, e)
        } catch (RestClientException e) {
            String msg = "Error (${path})"
            log.error(msg, e)
        }
        return null
    }
}


0

org.apache.http.wire cung cấp các bản ghi quá khó đọc, vì vậy tôi sử dụng nhật ký để đăng nhập ứng dụng Servlet và RestTemplate req / resp để đăng nhập

xây dựng. nâng cấp

compile group: 'org.zalando', name: 'logbook-spring-boot-starter', version: '1.13.0'

ứng dụng

logging.level.org.zalando.logbook:TRACE

Phần còn lại

@Configuration
public class RestTemplateConfig {

@Autowired
private LogbookHttpRequestInterceptor logbookHttpRequestInterceptor;

@Autowired
private LogbookHttpResponseInterceptor logbookHttpResponseInterceptor;

@Bean
public RestTemplate restTemplate() {
    return new RestTemplateBuilder()
        .requestFactory(new MyRequestFactorySupplier())
        .build();
}

class MyRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {

    @Override
    public ClientHttpRequestFactory get() {
        // Using Apache HTTP client.
        CloseableHttpClient client = HttpClientBuilder.create()
            .addInterceptorFirst(logbookHttpRequestInterceptor)
            .addInterceptorFirst(logbookHttpResponseInterceptor)
            .build();
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);
        return clientHttpRequestFactory;
    }

}
}

-1

Liên quan đến phản hồi bằng ClientHttpInterceptor, tôi đã tìm ra cách giữ toàn bộ phản hồi mà không cần đến các nhà máy Buffering. Chỉ lưu trữ luồng đầu vào của phần phản hồi bên trong mảng byte bằng cách sử dụng một số phương thức utils sẽ sao chép mảng đó từ phần thân, nhưng quan trọng, hãy bao quanh phương thức này với try Catch vì nó sẽ bị hỏng nếu phản hồi trống (đó là nguyên nhân của Ngoại lệ truy cập tài nguyên) và trong Catch chỉ tạo mảng byte trống và hơn là chỉ tạo lớp bên trong ẩn danh của ClientHttpResponse bằng cách sử dụng mảng đó và các tham số khác từ phản hồi ban đầu. Hơn bạn có thể trả đối tượng ClientHttpResponse mới đó vào chuỗi thực thi mẫu còn lại và bạn có thể ghi nhật ký phản hồi bằng cách sử dụng mảng byte cơ thể được lưu trữ trước đó. Bằng cách đó, bạn sẽ tránh tiêu thụ InputStream trong phản hồi thực tế và bạn có thể sử dụng phản hồi Mẫu còn lại. Ghi chú,


-2

cấu hình logger của tôi đã sử dụng xml

<logger name="org.springframework.web.client.RestTemplate">
    <level value="trace"/>
</logger>

sau đó bạn sẽ nhận được một cái gì đó như dưới đây:

DEBUG org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:92) : Reading [com.test.java.MyClass] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@604525f1]

thông qua HttpMessageConverterExtractor.java:92, bạn cần tiếp tục gỡ lỗi và trong trường hợp của tôi, tôi đã nhận được điều này:

genericMessageConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);

và điều này:

outputMessage.getBody().flush();

outputMessage.getBody () chứa thông điệp http (loại bài đăng) gửi


ghi nhật ký theo dõi có thể quá dài dòng ... nếu hàng ngàn yêu cầu mỗi giây thì sao?
Gervasio Amy
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.