spring
RestTemplate

RestTemplateでリクエストやレスポンスの中身をログに出す

More than 1 year has passed since last update.


目的

RestTemplateで外部API等を叩いたときのリクエストやレスポンスの情報をログに落とすやり方を調べてまとめた。


やり方は2通り


  1. RestTemplateのログレベルをDEBUGに設定する方法


  2. ClientHttpRequestInterceptorを使ってリクエストのタイミングで処理を差し込む方法

1のやり方は設定を足すだけなのでめちゃめちゃ簡単です。ただ、おそらくですが実装をさらっと見た感じではログの中身をカスタマイズできなそうなので、とりあえずログ見れればいいっすってときにはよさそうって感じ。

2のやり方は実装は必要でちょっと手間がかかりますが、任意の処理が実行可能なので好きな内容をログに出せます。

今回はログ用途で使いますが、ログに限らず任意の処理が差し込み可能です。

順番に試してみたいと思います。


環境

Java8

Spring 4.3.14.RELEASE

SpringBoot 1.5.14.RELEASE


1. RestTemplateのログレベルをDEBUGに設定する方法

設定ファイルに下記を追加する


src/main/resources/application.yml

logging:

level:
org.springframework.web.client.RestTemplate: DEBUG

動かしてみると下記のようなログが出ます

level:DEBUG Created GET request for "https://example.com"

level:DEBUG Setting request Accept header to [text/plain, text/plain, application/json, application/json, application/*+json, application/*+json, */*, */*]
level:DEBUG GET request for "https://example.com" resulted in 200 (OK)
level:DEBUG Reading [java.lang.String] as "text/html" using [org.springframework.http.converter.StringHttpMessageConverter@6a714237]

ざっと見た感じ出てる情報は以下


  • リクエスト先URL

  • リクエストヘッダ

  • ステータスコード

  • レスポンスのContent-Type

  • レスポンスボディに適用されるConverterの型とか


2. ClientHttpRequestInterceptorを使ってリクエストのタイミングで処理を差し込む方法


必要な作業をざっくりと



  1. org.springframework.http.client.ClientHttpRequestInterceptorインタフェースをimplementsしたクラスを作り、interceptメソッドにログ出力など任意の処理を実装する.

  2. RestTempalteに1で作ったInterceptorをaddしてあげて、呼ばれるようにする

以上です


やってみる


まずはClientHttpRequestInterceptorインタフェースを実装したクラスを作る

ClientHttpRequestInterceptorインタフェースを実装したクラスを下記の要領で作る。

@Slf4j

public class RestTemplateLoggingInterceptor implements ClientHttpRequestInterceptor {

@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
// リクエストの中身をログに落とす
log.info("RestTemplate Request: URI={}, Headers={}, Body={}",
request.getURI(),
request.getHeaders(),
new String(body));

// レスポンスを取得する
ClientHttpResponse response = new BufferingClientHttpResponseWrapper(execution.execute(request, body));

// レスポンスのボディを読み込む
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("RestTemplate Response: StatusCode={} {}, Headers={}, Body={}",
response.getStatusCode(),
response.getStatusText(),
response.getHeaders(),
inputStringBuilder.toString()
);

return response;
}

private static class BufferingClientHttpResponseWrapper implements ClientHttpResponse {

private final ClientHttpResponse response;

@Nullable
private byte[] body;

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

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

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

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

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

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

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

}
}

interceptメソッドの中身はよしなに変えれば、ログの中身をカスタマイズできます。

ネストクラスでBufferingClientHttpResponseWrapperを実装しているのは、レスポンスが1度しか読み込めないらしいのでラップするクラスを作っています。Springのソースを追っていたらorg.springframework.http.client.BufferingClientHttpRequestWrapperクラスがパッケージプライベートで存在していたので、コピペで使っちゃいました。


上で作ったInterceptorが呼ばれるようにする

RestTemplateBuilderを使ってRestTemplateを作るときに下記の要領で、自分で作ったRestTemplateLoggingInterceptorをaddすればOKです

RestTemplate restTemplate = restTemplateBuilder

.additionalInterceptors(new RestTemplateLoggingInterceptor()); // ここ
.build();

↑のやり方でも全然良いですが、「RestTemplateを使っている箇所全部に足すのはちょっと、、」って感じる場合は、下記のようにBean登録してあげると、使う側が何も気にしなくてよくなるので、こうやって使うのが良いんじゃないかと思います。

使う側はRestTemplateBuilderのインスタンスをDIコンテナから取得すれば、今回作ったRestTemplateLoggingInterceptorがaddされた状態で取得できるようになります。

@Slf4j

@Configuration
public class RestTemplateLoggingInterceptor implements ClientHttpRequestInterceptor {

@Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.additionalInterceptors(new RestTemplateLoggingInterceptor());
}

//.....下は割愛


使ってみる

使う側のコードイメージです。普通にRestTemplateを使うだけです。

@Component

public class Sample {

private final RestTemplate restTemplate;

public Sample(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder
.setConnectTimeout(1000)
.setReadTimeout(1000)
.build();
}

public String get() {
return restTemplate.getForObject("https://example.com", String.class);
}
}

動かしてみると下記の感じでログが出ます!

level:INFO  RestTemplate Request: URI=https://example.com, Headers={Accept=[text/plain, application/json, application/*+json, */*], Content-Length=[0]}, Body=

level:INFO RestTemplate Response: StatusCode=200 OK, Headers={Accept-Ranges=[bytes], Cache-Control=[max-age=604800], Content-Type=[text/html], Date=[Wed, 09 May 2018 08:23:02 GMT], Expires=[Wed, 16 May 2018 08:23:02 GMT], Last-Modified=[Fri, 09 Aug 2013 23:54:35 GMT], Body=<!doctype html>\n<html>\n</html>\n


参考

RestTepmlateのログが出したい | エンジニアっぽいことを書くブログ

java - Spring RestTemplate - how to enable full debugging/logging of requests/responses? - Stack Overflow