WebServiceTemplateのリクエスト・レスポンスをログ出力する

# 概要

Springフレームワークで提供されている`WebServiceTemplate`によるWebAPIの実行をログ出力したい。

## 環境

- Java 8
- SpringBoot 2.1.3.RELEASE
- Spring 5.1.5.RELEASE
- Lombok 1.18.6

## `WebServiceTemplate`に`ClientInterceptor`を差し込む

- `WebServiceTemplate`のリクエスト・レスポンスを参照するインターセプタを利用する
    - 送受されるHTTPヘッダ・ボディをログで出力

### ログ出力を行うインターセプタ

```java:WebServiceTemplateのインターセプタ
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.ws.client.WebServiceClientException;
import org.springframework.ws.client.support.interceptor.ClientInterceptor;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.transport.TransportOutputStream;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;

@Slf4j
public class WebServiceTemplateLoggingInterceptor implements ClientInterceptor {

    private final ThreadLocal<UUID> requestUuid = new ThreadLocal<>();

    @SneakyThrows
    @Override
    public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
        if (log.isInfoEnabled()) {
            requestUuid.set(UUID.randomUUID());
            val transportOutputStream = new ByteArrayTransportOutputStream();
            messageContext.getRequest().writeTo(transportOutputStream);
            log.info("[SOAP:Request({})] Headers=[{}], Body=[{}]",
                requestUuid.get(),
                transportOutputStream.getHeaders(),
                transportOutputStream.getBody()
            );
        }
        return true;
    }

    @SneakyThrows
    @Override
    public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
        if (log.isInfoEnabled()) {
            val transportOutputStream = new ByteArrayTransportOutputStream();
            messageContext.getResponse().writeTo(transportOutputStream);
            log.info("[SOAP:Response({})] Headers=[{}], Body=[{}]",
                requestUuid.get(),
                transportOutputStream.getHeaders(),
                transportOutputStream.getBody()
            );
        }
        return true;
    }

    @Override
    public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
        return true;
    }

    @Override
    public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException {
    }

    private static class ByteArrayTransportOutputStream extends TransportOutputStream {
        private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        @Getter
        private final MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();

        @Override
        public void addHeader(String name, String value) throws IOException {
            headers.add(name, value);
        }

        @Override
        protected OutputStream createOutputStream() throws IOException {
            return byteArrayOutputStream;
        }

        public String getBody() {
            return new String(byteArrayOutputStream.toByteArray());
        }
    }

}
```

### `WebServiceTemplate`のBean定義

- ログ出力を行うインターセプタを設定した`WebServiceTemplate`をBean定義する。

```java
@Configuration
public class ExternalApiConfig {

    @Bean
    WebServiceTemplate webServiceTemplate() {
        return new WebServiceTemplateBuilder()
            .additionalInterceptors(new WebServiceTemplateLoggingInterceptor())
            .build();
    }

}
```

# 参考

[Spring WS - Log Client Server HTTP Headers Example](https://codenotfound.com/spring-ws-log-client-server-http-headers.html)