目的
RestTemplateで外部API等を叩いたときのリクエストやレスポンスの情報をログに落とすやり方を調べてまとめた。
やり方は2通り
- RestTemplateのログレベルをDEBUGに設定する方法
-
ClientHttpRequestInterceptor
を使ってリクエストのタイミングで処理を差し込む方法
1のやり方は設定を足すだけなのでめちゃめちゃ簡単です。ただ、おそらくですが実装をさらっと見た感じではログの中身をカスタマイズできなそうなので、とりあえずログ見れればいいっすってときにはよさそうって感じ。
2のやり方は実装は必要でちょっと手間がかかりますが、任意の処理が実行可能なので好きな内容をログに出せます。
今回はログ用途で使いますが、ログに限らず任意の処理が差し込み可能です。
順番に試してみたいと思います。
環境
Java8
Spring 4.3.14.RELEASE
SpringBoot 1.5.14.RELEASE
1. RestTemplateのログレベルをDEBUGに設定する方法
設定ファイルに下記を追加する
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
を使ってリクエストのタイミングで処理を差し込む方法
必要な作業をざっくりと
-
org.springframework.http.client.ClientHttpRequestInterceptor
インタフェースをimplementsしたクラスを作り、intercept
メソッドにログ出力など任意の処理を実装する. - 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