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

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

## やり方は2通り
1. RestTemplateのログレベルをDEBUGに設定する方法
2. `ClientHttpRequestInterceptor`を使ってリクエストのタイミングで処理を差し込む方法

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

2のやり方は実装は必要でちょっと手間がかかりますが、任意の処理が実行可能なので好きな内容をログに出せます。
今回はログ用途で使いますが、ログに限らず任意の処理が差し込み可能です。

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

## 環境
Java8
Spring 4.3.14.RELEASE
SpringBoot 1.5.14.RELEASE

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

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

```yaml: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`インタフェースを実装したクラスを下記の要領で作る。

```java
@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です

```java
RestTemplate restTemplate = restTemplateBuilder
    .additionalInterceptors(new RestTemplateLoggingInterceptor()); // ここ
    .build();
```

↑のやり方でも全然良いですが、「RestTemplateを使っている箇所全部に足すのはちょっと、、」って感じる場合は、下記のようにBean登録してあげると、使う側が何も気にしなくてよくなるので、こうやって使うのが良いんじゃないかと思います。
使う側は`RestTemplateBuilder`のインスタンスをDIコンテナから取得すれば、今回作った`RestTemplateLoggingInterceptor`がaddされた状態で取得できるようになります。

```java
@Slf4j
@Configuration
public class RestTemplateLoggingInterceptor implements ClientHttpRequestInterceptor {

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

    //.....下は割愛
```

### 使ってみる

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

```java
@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のログが出したい | エンジニアっぽいことを書くブログ](https://ishiis.net/2018/02/17/rest-tepmlate-logging/)
[java - Spring RestTemplate - how to enable full debugging/logging of requests/responses? - Stack Overflow](https://stackoverflow.com/questions/7952154/spring-resttemplate-how-to-enable-full-debugging-logging-of-requests-responses)