拦截Spring的RestTemplate打印日志

简单使用

@Component
@Slf4j
public class RestTemplateBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof RestTemplate) {
            RestTemplate restTemplate = (RestTemplate) bean;
            List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(restTemplate.getInterceptors());
            restTemplate.setInterceptors(Collections.emptyList());
            //可以多次调用ClientHttpResponse的getBody()方法
            BufferingClientHttpRequestFactory requestFactory = new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory());
            restTemplate.setRequestFactory(requestFactory);
            //打印日志
            interceptors.add(new WebLogInterceptor());
            restTemplate.setInterceptors(interceptors);
        }
        return bean;
    }

    static class WebLogInterceptor implements ClientHttpRequestInterceptor {

        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            long start = System.currentTimeMillis();
            try {
                ClientHttpResponse response = execution.execute(request, body);
                RequestInfo requestInfo = createRequestInfo(request, body, response, start);
                log.info("Request Info      : {}", JSON.toJSONString(requestInfo));
                return response;
            } catch (Throwable e) {
                RequestErrorInfo requestErrorInfo = createRequestErrorInfo(request, body, e);
                log.error("Error Request Info      : {}", JSON.toJSONString(requestErrorInfo), e);
                throw e;
            }
        }
    }

    private static RequestInfo createRequestInfo(HttpRequest request, byte[] body, ClientHttpResponse response, long start)
            throws IOException {
        long cost = System.currentTimeMillis() - start;
        String result = IOUtils.toString(response.getBody(), StandardCharsets.UTF_8);
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setUrl(request.getURI().toString());
        requestInfo.setHttpMethod(request.getMethodValue());
        try {
            MediaType requestContentType = request.getHeaders().getContentType();
            if (Objects.nonNull(requestContentType) &&
                    requestContentType.toString().startsWith("application/json")) {
                requestInfo.setRequestParams(JSON.parseObject(new String(body)));
            } else {
                requestInfo.setRequestParams(new String(body));
            }
            MediaType responseContentType = response.getHeaders().getContentType();
            if (Objects.nonNull(responseContentType) &&
                    responseContentType.toString().startsWith("application/json")) {
                requestInfo.setResult(JSON.parseObject(result));
            } else {
                requestInfo.setResult(result);
            }
        } catch (Exception e) {
            log.error("创建RequestInfo错误,", e);
            requestInfo.setRequestParams(new String(body));
            requestInfo.setResult(result);
        }
        requestInfo.setTimeCost(cost);
        return requestInfo;
    }

    private static RequestErrorInfo createRequestErrorInfo(HttpRequest request, byte[] body, Throwable throwable) {
        RequestErrorInfo requestErrorInfo = new RequestErrorInfo();
        requestErrorInfo.setUrl(request.getURI().toString());
        requestErrorInfo.setHttpMethod(request.getMethodValue());
        requestErrorInfo.setExceptionMessage(throwable.getMessage());
        try {
            MediaType requestContentType = request.getHeaders().getContentType();
            if (Objects.nonNull(requestContentType) &&
                    requestContentType.toString().startsWith("application/json")) {
                requestErrorInfo.setRequestParams(JSON.parseObject(new String(body)));
            } else {
                requestErrorInfo.setRequestParams(new String(body));
            }
        } catch (Exception e) {
            log.error("创建RequestErrorInfo错误,", e);
            requestErrorInfo.setRequestParams(new String(body));
        }
        return requestErrorInfo;
    }

    @Data
    public static class RequestInfo {
        private String url;
        private String httpMethod;
        private Object requestParams;
        private Object result;
        private Long timeCost;
    }

    @Data
    public static class RequestErrorInfo {
        private String url;
        private String httpMethod;
        private Object requestParams;
        private String exceptionMessage;
    }
}

通过后置处理器(BeanPostProcessor)拦截 RestTemplate 的初始化过程,给 RestTemplate 添加拦截器。

遇到的问题

问题1

Caused by: java.io.IOException: Attempted read from closed stream.

主要是因为 response.getBody() 只能获取一次,在后续 RestTemplate 获取输出转化时会再次调用 response.getBody(),所以我们需要包装一下。

//可以多次调用ClientHttpResponse的getBody()方法
BufferingClientHttpRequestFactory requestFactory = new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory());

问题2

我们在调用 restTemplate.getRequestFactory() 前必须先调用 restTemplate.setInterceptors(Collections.emptyList()),不然如果之前已经有拦截器了,那么 interceptingRequestFactory 会被包装两次,所有拦截器都会执行两次。

RestTemplate restTemplate = (RestTemplate) bean;
restTemplate.getInterceptors().add(new TestInterceptor()); // 模拟在其他地方设置了拦截器
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(restTemplate.getInterceptors());
//        restTemplate.setInterceptors(Collections.emptyList()); // 如果不置空就会有问题
//可以多次调用ClientHttpResponse的getBody()方法
BufferingClientHttpRequestFactory requestFactory = new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory());
restTemplate.setRequestFactory(requestFactory);
//打印日志
interceptors.add(new WebLogInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;

参考

RestTemplate 打印日志的正确姿势

posted @ 2024-03-03 23:18  strongmore  阅读(42)  评论(0编辑  收藏  举报