Spring RestTemplate-요청 / 응답의 전체 디버깅 / 로깅을 활성화하는 방법은 무엇입니까?


220

나는 Spring RestTemplate을 오랫동안 사용 해 왔으며 요청과 응답을 디버깅하려고 할 때 지속적으로 벽에 부딪쳤다. 나는 기본적으로 "verbose"옵션이 켜진 상태에서 curl을 사용할 때와 같은 것을보고자합니다. 예를 들면 다음과 같습니다.

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

전송 된 데이터와 수신 된 데이터 (헤더, 쿠키 등 포함)를 모두 표시합니다.

다음과 같은 관련 게시물을 확인했습니다 : Spring RestTemplate에서 응답을 어떻게 기록합니까? 하지만이 문제를 해결하지 못했습니다.

이를 수행하는 한 가지 방법은 실제로 RestTemplate 소스 코드를 변경하고 거기에 몇 가지 로깅 문을 추가하는 것이지만,이 방법은 실제로 최후의 수단이라는 것을 알 수 있습니다. Spring Web Client / RestTemplate에 모든 것을 훨씬 친숙한 방식으로 기록하도록 지시하는 방법이 있어야합니다.

내 목표는 다음과 같은 코드 로이 작업을 수행하는 것입니다.

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

그런 다음 로그 파일이나 콘솔에서 동일한 유형의 디버그 정보를 가져옵니다 (컬과 함께). Spring RestTemplate을 사용하고 문제가있는 사람에게는 이것이 매우 유용 할 것이라고 생각합니다. curl을 사용하여 RestTemplate 문제를 디버그하는 것은 효과가 없습니다 (경우에 따라).


31
2018 년에 읽는 사람에게 경고 : 이에 대한 간단한 대답은 없습니다!
davidfrancis

3
가장 쉬운 방법은 AbstractHttpMessageConverter 클래스의 write (...) 메소드에 중단 점을 사용하는 것입니다. 데이터를 볼 수있는 outputMessage 객체가 있습니다. PS 값을 복사 한 후 온라인 포맷터로 형식화 할 수 있습니다.
Sergey Chepurnov

1
이것은 봄에하기 쉬운 것처럼 보이지만 여기서는 대답이 아닙니다. 따라서 다른 솔루션은 Spring을 완전히 우회하고 Fiddler와 같은 도구를 사용하여 요청 / 응답을 캡처하는 것입니다.
michaelok


2019 년 7 월 :이 질문에 대한 간단한 해결책이 아직 없기 때문에 다른 24 가지 답변 (지금까지)과 그에 대한 의견 및 토론을 아래 내 자신의 답변 으로 요약하려고했습니다 . 도움이 되길 바랍니다.
Chris

답변:


207

ClientHttpRequestInterceptor요청과 응답을 추적하기 위한 전체 구현으로 예제를 완성하기 만하면됩니다 .

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

}

그런 다음 RestTemplatea BufferingClientHttpRequestFactoryLoggingRequestInterceptor:를 사용하여 인스턴스화 하십시오 .

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

BufferingClientHttpRequestFactory인터셉터와 초기 호출 코드 모두에 응답 본문을 사용하려면 이 값 이 필요합니다. 기본 구현에서는 응답 본문을 한 번만 읽을 수 있습니다.


27
이것은 잘못이다. 스트림을 읽으면 애플리케이션 코드가 응답을 읽을 수 없습니다.
James Watkins

28
응답을 두 번 읽을 수 있도록 RestTemplate에 BufferingClientHttpRequestFactory를 제공했습니다.
sofiene zaghdoudi

16
우리는이 기술을 현재 약 3 개월 동안 사용해 왔습니다. BufferingClientHttpResponseWrapper@sofienezaghdoudi가 암시 하는 것처럼 구성된 RestTemplate에서만 작동 합니다. 그러나 Spring의 mockServer 프레임 워크를 사용하는 테스트 MockRestServiceServer.createServer(restTemplate)에서 RequestFactory를로 덮어 쓰기 때문에 사용하면 작동하지 않습니다 InterceptingClientHttpRequestFactory.
RubesMN

8
기술은 양호하고 구현이 잘못되었습니다. 404 case, response.getBody () throw IOException-> 절대 로그를 얻지 못하고 최악의 경우, RestClientResponseException 대신에 추가 코드에서 ResourceAccessException이됩니다
MilacH

6
답장을 보내 주셔서 감사합니다. 그러나 이것은 많은 다른 로그에 분산 될 수 있으므로 여러 개의 "log.debug"를 갖는 것은 좋지 않습니다. 하나의 log.debug 명령어를 사용하는 것이 좋습니다. 모든 것이 같은 곳에 있는지 확인하십시오
user2447161

129

Spring Boot에서는 속성 (또는 다른 12 요소 방법)에서 이것을 설정하여 전체 요청 / 응답을 얻을 수 있습니다

logging.level.org.apache.http=DEBUG

이 출력

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

그리고 응답

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

또는 logging.level.org.apache.http.wire=DEBUG관련 정보가 모두 포함 된 것 같습니다


4
이것이 내가 원하는 것을 한 가장 간단한 일이었습니다. 나는 이것을 받아 들인 대답에 포함시키는 것이 좋습니다.
michaelavila

22
RestTemplate 의 javadoc에 따르면 :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은 @OrtomalaLokni가 지적한 것처럼 이러한 Apache 클래스를 기본값으로 사용하지 않으므로 사용 중에 디버그를 인쇄하는 방법과 함께 사용하는 방법 도 포함시켜야 합니다 .
Captain Man

나는 다음과 같이되고있다 :http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh

2
@ParthaSarathiGhosh 내용이 아마도 gzip으로 인코딩되어 있으므로 원본 텍스트가 보이지 않습니다.
Matthew Buckett

79

일부 코드로 @hstoerr 답변을 확장 :


요청 응답을 기록하는 LoggingRequestInterceptor 작성

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

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

spring-3.1 버전까지는 사용할 수 없습니다.
Gyan

3
'응답 로깅'에 대한 질문에는 답하지 않고 // 로깅 로깅 주석을 남겨 둡니다.
Jiang YD

1
로깅을 수행하는 것은 쉽지만 요청에 대해서만 작동합니다. 응답 본문이 표시되지 않고 응답 객체가 있다고 가정하지만 스트림을 읽는 것이 좋지 않습니다.
Pavel Niedoba

11
@PavelNiedoba BufferClientHttpRequestFactory를 사용하면 응답을 두 번 이상 읽을 수 있습니다.
mjj1409

2
디버깅을 위해 데이터베이스에 요청 / 응답에 대한 정보를 저장해야하고 정기적 인 로깅이 사용자의 요구에 맞지 않는 경우에 효과적입니다.
GameSalutes

32

가장 좋은 방법은 파일 에 추가 logging.level.org.springframework.web.client.RestTemplate=DEBUG하는 것 application.properties입니다.

설정과 같은 다른 솔루션 log4j.logger.httpclient.wire은 항상 사용 하지 않고 log4jApache 를 사용한다고 가정하기 때문에 항상 작동 HttpClient하지는 않습니다.

그러나이 구문은 최신 버전의 Spring Boot에서만 작동합니다.


5
이것은 요청 및 응답 본문을 기록하는 것이 아니라 URL 및 요청 유형 (spring-web-4.2.6)
dve

1
당신은 맞습니다, 그것은 wire로깅 이 아니며 , 그것은 URL, 응답 코드, POST 매개 변수 등과 같은 필수 정보만을 포함합니다.
gamliela

1
당신이 정말로 원하는 것은이 stackoverflow.com/a/39109538/206466
xenoterracide

이것은 괜찮지 만 응답 본문을 볼 수 없습니다!
sunleo

훌륭한. 응답 본문을 인쇄하지는 않지만 여전히 매우 유용합니다. 감사합니다.
Chris

30

이 답변 중 어느 것도 실제로 문제의 100 %를 해결하지 못합니다. mjj1409는 대부분을 얻지 만 응답 로깅 문제를 편리하게 피할 수 있으므로 조금 더 많은 작업이 필요합니다. Paul Sabou는 현실적으로 보이지만 실제로 구현하기에 충분한 세부 정보를 제공하지 않는 솔루션을 제공합니다 (나에게 전혀 효과가 없었습니다). Sofiene은 로깅을 얻었지만 중요한 문제가 있습니다. 입력 스트림이 이미 소비되었으므로 응답을 더 이상 읽을 수 없습니다!

응답 본문을 여러 번 읽을 수 있도록 BufferingClientHttpResponseWrapper를 사용하여 응답 객체를 래핑하는 것이 좋습니다.

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

}

응답 본문이 메모리에로드되어 여러 번 읽을 수 있기 때문에 InputStream을 사용하지 않습니다. 클래스 경로에 BufferingClientHttpResponseWrapper가없는 경우 여기에서 간단한 구현을 찾을 수 있습니다.

https://github.com/spring-projects/spring-android/blob/master/spring-android-rest-template/src/main/java/org/springframework/http/client/BufferingClientHttpResponseWrapper.java

RestTemplate을 설정하려면 다음을 수행하십시오.

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

같은 방법으로 responseCopy.getBody ()는 404의 경우 IOexception을 던지므로 응답을 더 이상 코드로 다시 보내지 않으며 일반적으로 RestClientResponseException은 ResourceAccessException이됩니다.
MilacH

1
status==200이전에 확인해야합니다responseCopy.getBody()
Anand Rockzz

4
그러나 패키지 전용입니다. LoggingRequestInterceptor를 'org.springframework.http.client'패키지에 넣었습니까?
zbstof 2016 년

2
무엇에 대해 asyncRestTemplate? 콜백에서 ListenableFuture변경할 수없는 인터셉트 할 때 를 반환해야합니다 BufferingClientHttpResponseWrapper.
Ömer Faruk Almalı

@ ÖmerFarukAlmalı이 경우 사용중인 구아바 버전에 따라 체인을 사용하거나 변환해야합니다. 참조 : stackoverflow.com/questions/8191891/...
제임스 왓킨스

30

spring-rest-template-logger 를 사용하여 RestTemplateHTTP 트래픽 을 기록 할 수 있습니다 .

Maven 프로젝트에 종속성을 추가하십시오.

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

그런 RestTemplate다음 다음과 같이 사용자 정의하십시오 .

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

다음에서 디버그 로깅이 사용 가능한지 확인하십시오 application.properties.

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

이제 모든 RestTemplate HTTP 트래픽이 org.hobsoft.spring.resttemplatelogger.LoggingCustomizer 디버그 수준 .

면책 조항 : 나는이 도서관을 썼습니다.


이 답변이 다운 보팅되는 이유는 무엇입니까? 그것은 나를 도왔다. 감사합니다, @Mark Hobson.
Raffael Bechara Rameh

3
@RaffaelBecharaRameh이 (가) 도움이 된 것을 기쁘게 생각합니다. 연결된 프로젝트의 지침을 포함하지 않았기 때문에 처음에는 다운 보트되었습니다. 유용하다고 생각되면 자유롭게 투표하십시오!
마크 홉슨

Gradle을 통해 지원합니까?
BlackHatSamurai 2016 년

1
@BlackHatSamurai 스프링 레스트 템플릿 로거는 일반적인 Maven 인공물이므로 Gradle과 잘 작동합니다.
Mark Hobson

1
안녕하세요 @erhanasikoglu, 천만에요! 맞습니다. github.com/markhobson/spring-rest-template-logger/blob/master/…
Mark Hobson

29

xenoterracide가 제공하는 솔루션

logging.level.org.apache.http=DEBUG

좋지만 문제는 기본적으로 Apache HttpComponents가 사용되지 않는다는 것입니다.

Apache HttpComponents를 사용하려면 pom.xml에 추가하십시오.

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

다음과 같이 구성하십시오 RestTemplate.

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

가장 쉬운 방법은 requestFactory를 덮어 쓰므로 MockRestServiceServer에서 작동하지 않는다는 것입니다.
zbstof 2006 년

잘 작동하고 문제가 덜 구성!
sunleo

26

마침내 올바른 방법으로이 작업을 수행 할 수있는 방법을 찾았습니다. 솔루션의 대부분은 로깅을 얻을 수 있도록 Spring 및 SLF4J어떻게 구성합니까?

수행해야 할 두 가지가 있습니다.

  1. log4j.properties에 다음 행을 추가하십시오. log4j.logger.httpclient.wire=DEBUG
  2. 스프링이 로깅 설정을 무시하지 않는지 확인하십시오

두 번째 문제는 주로 slf4j가 사용되는 스프링 환경에서 발생합니다 (내 경우처럼). 따라서 slf4j를 사용하는 경우 다음 두 가지 상황이 발생하는지 확인하십시오.

  1. 클래스 경로에는 커먼즈 로깅 라이브러리가 없습니다 : 이것은 pom에 제외 설명자를 추가하여 수행 할 수 있습니다.

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. log4j.properties 파일은 스프링이 찾을 수있는 클래스 경로의 어딘가에 저장됩니다. 이것에 문제가 있다면, 최후의 해결책은 log4j.properties 파일을 기본 패키지에 넣는 것입니다 (좋은 습관은 아니지만 예상대로 작동하는지 확인하는 것입니다)


7
이것은 나를 위해 작동하지 않습니다, 나는 두 가지를 모두했습니다. 내 프로젝트에 어쨌든 사용하지 않을 때 나는 log4j.properties를 둘 필요가 왜 이해가 안 (MVN 의존성에 의해 확인 : 나무)
파벨 Niedoba

이것은 나에게도 효과가 없다. 루트 로거를 디버그 모드로 설정했지만 여전히 아무것도 시도하지 않았습니다.
James Watkins 2016 년

"httpclient.wire.content"및 "httpclient.wire.header"는 Axis2 프레임 워크의 로거 이름입니다. 그것들은 Axis2를 사용하여 수행되는 경우 Spring 프로젝트에서 SOAP 요청을 기록하는 데 사용될 수 있습니다 .
라스 스펠

11
httpclient.wire실제로 Apache HttpComponents HttpClient 라이브러리에서 온 것입니다 ( hc.apache.org/httpcomponents-client-ga/logging.html 참조 ). 이 기술은 다음 RestTemplate을 사용하도록 구성한 경우에만 작동합니다 .HttpComponentsClientHttpRequestFactory
Scott Frederick

22

RestTemplate 로깅

옵션 1. 디버그 로깅을 엽니 다.

RestTemplate 구성

  • 기본적으로 RestTemplate 은 표준 JDK 기능을 사용하여 HTTP 연결을 설정합니다. Apache HttpComponents와 같은 다른 HTTP 라이브러리를 사용하도록 전환 할 수 있습니다

    @Bean public RestTemplate restTemplate (RestTemplateBuilder 빌더) {RestTemplate restTemplate = builder.build (); restTemplate을 반환; }

로깅 구성

  • application.yml

    로깅 : 레벨 : org.springframework.web.client.RestTemplate : DEBUG

옵션 2. 인터셉터 사용

래퍼 응답

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

인터셉터 구현

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

}

RestTemplate 구성

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

로깅 구성

  • LoggingRestTemplate 패키지를 확인하십시오 (예 application.yml:

    로깅 : 레벨 : com.example.logging : DEBUG

옵션 3. httpcomponent 사용

httpcomponent 의존성 가져 오기

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

RestTemplate 구성

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

로깅 구성

  • LoggingRestTemplate 패키지를 확인하십시오 (예 application.yml:

    로깅 : 레벨 : org.apache.http : DEBUG


그냥 참고 : 구성하려는 경우 TestRestTemplate, 구성 RestTemplateBuilder:. @Bean 공공 RestTemplateBuilder restTemplateBuilder () {새로운 RestTemplateBuilder를 반환 () additionalInterceptors (Collections.singletonList (새 LoggingRestTemplate ())); }
kingoleg

또한 새로운 InputStreamReader (responseWrapper.getBody (), StandardCharsets.UTF_8)); "다른 쪽 끝"이 오류를 반환하면 오류가 발생할 수 있습니다. try 블록에 배치 할 수 있습니다.
PeterS

16

---- 2019 년 7 월 ----

(스프링 부트 사용)

Spring Boot는 Zero Configuration 마법으로 RestTemplate을 사용하여 간단한 JSON 응답 본문을 검사하거나 기록하는 쉬운 방법을 제공하지 않는다는 사실에 놀랐습니다. 여기에 제공된 다양한 답변과 의견을 살펴본 결과 현재 작동하는 옵션 (여전히)의 증류 버전을 공유하고 있으며 현재 옵션 (Gradle 4.4와 함께 Spring Boot 2.1.6을 사용하고 있습니다) )

1. Fiddler를 http 프록시로 사용

이것은 실제로 자신의 인터셉터를 만들거나 기본 http 클라이언트를 아파치로 변경하는 모든 번거로운 노력을 우회하기 때문에 실제로 매우 우아한 솔루션입니다 (아래 참조).

Fiddler 설치 및 실행

그리고

-DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888VM 옵션에 추가

2. Apache HttpClient 사용

Maven 또는 Gradle 종속성에 Apache HttpClient를 추가하십시오.

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

HttpComponentsClientHttpRequestFactoryRestTemplate에 대한 RequestFactory로 사용하십시오 . 가장 간단한 방법은 다음과 같습니다.

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

에서 디버그 활성화 application.properties파일 하십시오 (Spring Boot를 사용하는 경우)

logging.level.org.apache.http=DEBUG

Spring Boot를 사용하는 경우, 예를 들어 spring-boot-starter 종속성을 사용하여 로깅 프레임 워크를 설정해야합니다. spring-boot-starter-logging .

3. 인터셉터 사용

다른 답변과 의견에서 제안서, 반대 제안서 및 문제를 읽고 그 길을 가고 싶은지 스스로 결정하도록하겠습니다.

4. 본문이없는 로그 URL 및 응답 상태

이는 본문 로깅의 명시된 요구 사항을 충족하지 않지만 REST 호출 로깅을 시작하는 빠르고 간단한 방법입니다. 전체 URL 및 응답 상태를 표시합니다.

application.properties파일에 다음 줄을 추가하기 만하면 됩니다 (Spring Boot를 사용한다고 가정하고을 포함하는 Spring Boot Starter 종속성을 사용한다고 가정 spring-boot-starter-logging)

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

결과는 다음과 같습니다.

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
No. 4는 디버깅하는 가장 쉬운 방법입니다.
Yubaraj

1
2 번은 나를 위해 일했다. 요청 본문을 기록합니다. 감사합니다!
caglar

1
나는이 문제에 올 때이 작업을 수행하는 쉬운 방법을 3 번 발견했습니다.
Bill Naylor

12

다른 답변에 설명 HttpClient 로깅 외에도 요청 본문과 응답을 읽고 기록하는 ClientHttpRequestInterceptor를 도입 할 수도 있습니다. 다른 것들도 HttpClient를 사용하거나 사용자 정의 로깅 형식을 원할 경우이 작업을 수행 할 수 있습니다. 주의 : 응답을 두 번 읽을 수 있도록 RestTemplate에 BufferingClientHttpRequestFactory를 제공하려고합니다.


12

다른 응답에서 언급했듯이 응답 본문은 특별한 처리가 필요하므로 반복해서 읽을 수 있습니다 (기본적으로 내용은 첫 번째 읽기에서 소비 됨).

BufferingClientHttpRequestFactory요청을 설정할 때 를 사용하는 대신 인터셉터 자체가 응답을 랩핑하고 컨텐츠를 보유하고 반복적으로 읽을 수 있습니다 (로거 및 응답 소비자가).

내 요격기

  • 랩퍼를 사용하여 응답 본문버퍼링합니다.
  • A의 로그를 보다 컴팩트 한 방법
  • 상태 코드 식별자 도 기록합니다 (예 : 201 Created)
  • 동시 로그 항목을 여러 스레드와 쉽게 구별 할 수 있는 요청 순서 번호 포함

암호:

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

구성 :

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

로그 출력 예 :

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
이것은 신체를 그대로 유지하는 2019 봄 버전에서 작동합니다.
우도

1
Spring 2.1.10에서 작동 :) 감사합니다
Moler

8

application.properties

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

application.yml

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

8

이것은 올바른 방법이 아닐 수도 있지만 로그를 너무 많이 채우지 않고 요청과 응답을 인쇄하는 가장 간단한 방법이라고 생각합니다.

application.properties 아래에 두 줄을 추가하면 요청을 기록하기 위해 모든 요청과 응답을 첫 번째 줄에 기록하고 두 번째 줄은 응답을 기록합니다.

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

로깅 응답이 작동하지 않습니다. 상태 코드 만 기록합니다. 페이로드를 기록해야합니까?
badera

HttpEntityMethodProcessor (v5.1.8) 클래스는 아무것도 기록하지 않습니다.
Chris


4

여기에 많은 응답이 코딩 변경 및 사용자 정의 클래스가 필요하며 실제로 필요하지 않습니다. fiddler와 같은 디버깅 프록시를 사용하고 명령 행 (-Dhttp.proxyHost 및 -Dhttp.proxyPort)에서 프록시를 사용하도록 Java 환경을 설정 한 후 fiddler를 실행하면 요청 및 응답을 모두 볼 수 있습니다. 또한 서버를 수정하기 전에 실험을 실행하기 전과 후에 결과와 응답을 수정하는 기능과 같은 많은 부수적 인 장점이 있습니다.

발생할 수있는 마지막 문제는 HTTPS를 사용해야하는 경우 fiddler에서 SSL 인증서를 내보내고 java 키 저장소 (cacerts) 힌트로 가져와야합니다. 기본 java 키 저장소 비밀번호는 일반적으로 "changeit"입니다.


1
이것은 intellij와 정기적 인 바이올린 설치를 사용하여 나를 위해 일했습니다. Run Configuration을 편집하고 VM 옵션을로 설정했습니다 -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD

감사! 이것은 자신의 인터셉터를 작성하는 것과 비교할 때 매우 우아한 솔루션입니다.
Chris

3

Apache HttpClient의 도움 으로 Logback에 로깅하는 경우 :

클래스 경로에 Apache HttpClient가 필요합니다.

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

RestTemplateHttpClient를 사용하도록 구성하십시오 .

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

요청 및 응답을 로그하려면 로그 백 구성 파일에 추가하십시오.

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

또는 더 많이 기록하려면

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

어떤 로그 백 구성 파일?
G_V

1
테스트의 경우 @G_V logback.xml 또는 logback-test.xml
holmis83

또한 작동 org.apache.http.wire=DEBUG당신의 application.properties지금
G_V

Spring-Boot를 사용하는 경우 @G_V 내 대답은 부팅하지 않고 작동합니다.
holmis83

2

당신을 구성하는 트릭 RestTemplate로모그래퍼은 BufferingClientHttpRequestFactory당신이 어떤을 사용하는 경우 작동하지 않습니다 ClientHttpRequestInterceptor당신이 인터셉터를 통해 로그인하려고한다면, 이는. 이것은 InterceptingHttpAccessor( RestTemplate서브 클래스가) 작동 하는 방식 때문 입니다.

간단히 말해서 ... 대신이 클래스를 RestTemplate사용하십시오 (SLF4J 로깅 API를 사용하고 필요에 따라 편집하십시오).

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

나는 이것을하기 위해 많은 노력이 필요하다는 것은 어리석은 일에 동의합니다.


2

위의 논의에 덧붙여 이것은 행복한 시나리오만을 나타냅니다. 오류 가 발생하면 응답을 기록 할 수 없습니다 .

이 경우와 위의 모든 경우에 DefaultResponseErrorHandler 를 재정의 하고 아래와 같이 설정해야합니다.

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());

2

이상하게도 RestTemplate이 일부 클라이언트 및 서버 500x 오류에 대한 응답을 반환하지 않는 것처럼 이러한 솔루션 중 어느 것도 작동하지 않습니다. 이 경우 다음과 같이 ResponseErrorHandler를 구현하여 로그를 기록합니다. 초안 코드는 다음과 같습니다.

오류 처리기와 동일한 인터셉터를 설정할 수 있습니다.

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

그리고 인터셉트는 두 인터페이스를 모두 구현합니다.

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

본문이 multipart / form-data 인 경우 로그에서 이진 데이터 (파일 내용)를 쉽게 필터링 할 수 있습니까?
Luke

1

@MilacH가 지적했듯이 구현에 오류가 있습니다. statusCode> 400이 리턴되면 errorHandler가 호출되지 않아 인터셉터에서 IOException이 발생합니다. 예외는 무시할 수 있으며 처리기 메서드에서 다시 포착됩니다.

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

지금 최고의 솔루션, 단지 의존성을 추가하십시오 :

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

여기에는 RestTemplate에 이런 방식으로 추가 할 수있는 LoggingRequestInterceptor 클래스가 포함되어 있습니다.

이 유틸리티를 다음과 같은 방식으로 스프링 RestTemplate에 인터셉터로 추가하여이 유틸리티를 통합하십시오.

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

log4j와 같은 프레임 워크에 slf4j 구현을 추가하십시오.

또는 "Zg2proRestTemplate"을 직접 사용하십시오 . @PaulSabou의 "최고의 답변"은 httpclient와 모든 apache.http 라이브러리가 스프링 RestTemplate을 사용할 때 반드시로드되지 않기 때문에 그렇게 보입니다.


출시 된 버전은 무엇입니까?
popalka

출시 된 버전은 0.2입니다.
Moses Meyer

1
사용의 용이성은 훌륭하지만 헤더가 부족합니다
WrRaThY

추가적으로 : LoggingRequestInterceptor의 모든 유용한 메소드는 비공개이며, 이는 확장과 관련하여 문제가됩니다 (보호 될 수 있음)
WrRaThY

슬프게도 5 분 후에는 댓글을 편집 할 수 없습니다. 헤더를 기록하기 위해해야 ​​할 일은 log("Headers: {}", request.headers)in LoggingRequestInterceptor:traceRequestlog("Headers: {}", response.headers)in LoggingRequestInterceptor:logResponse입니다. 헤더 및 본문 로깅을위한 일부 플래그를 추가 할 수 있습니다. 또한 로깅을 위해 본문 컨텐츠 유형을 점검 할 수 있습니다 (예 : log only application / json *). 또한 구성 할 수 있어야합니다. 그 작은 조정만으로도 훌륭한 라이브러리가 퍼질 것입니다. 잘 했어 :)
WrRaThY

0

이것에 대한 구현을 추가하고 싶었습니다. 누락 된 모든 세미콜론에 대해 사과드립니다. 이것은 Groovy로 작성되었습니다.

제공된 답변보다 더 구성 가능한 것이 필요했습니다. 다음은 매우 민첩하며 OP가 찾고있는 모든 것을 기록하는 나머지 템플릿 bean입니다.

사용자 정의 로깅 인터셉터 클래스 :

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=================================================")
        }
    }
}

나머지 템플리트 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
}

이행:

@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 가 읽을 수없는 로그를 제공하므로 로그 북 을 사용 하여 애플리케이션 Servlet 및 RestTemplate req / resp를 로그에 기록합니다.

build.gradle

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

application.properties

logging.level.org.zalando.logbook:TRACE

RestTemplate

@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

ClientHttpInterceptor를 사용한 응답과 관련하여 버퍼링 팩토리없이 전체 응답을 유지하는 방법을 찾았습니다. 본문에서 해당 배열을 복사하는 일부 utils 메소드를 사용하여 응답 본문 입력 스트림을 바이트 배열 안에 저장하십시오. 중요한 것은 응답이 비어 있으면 (자원 액세스 예외의 원인 인 경우) 중단되기 때문에 try catch로이 메소드를 둘러싸십시오. catch에서 빈 바이트 배열을 만들고 해당 배열과 원래 응답의 다른 매개 변수를 사용하여 익명의 ClientHttpResponse 클래스를 만드는 것보다. 새 ClientHttpResponse 오브젝트를 나머지 템플리트 실행 체인으로 리턴 할 수 있고 이전에 저장된 본문 바이트 배열을 사용하여 응답을 로그 할 수 있습니다. 이렇게하면 실제 응답에서 InputStream을 사용하지 않고 Rest Template 응답을 그대로 사용할 수 있습니다. 노트,


-2

내 로거 구성에서 XML을 사용했습니다.

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

다음과 같은 것을 얻을 수 있습니다 :

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]

HttpMessageConverterExtractor.java:92를 통해 디버깅을 계속해야하며 제 경우에는 이것을 얻었습니다.

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

이:

outputMessage.getBody().flush();

outputMessage.getBody ()는 http (post type)이 보낸 메시지를 포함합니다


추적 로깅이 너무 장황 할 수 있습니다. 초당 수천 개의 요청이있는 경우 어떻게해야합니까?
Gervasio Amy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.