使用restTemlate 时,如何优雅而全面地记录请求参数和返回值到日志

使用 RestTemplate 时,记录请求参数和返回值到日志是调试和维护的重要环节。为了优雅而全面地实现这一功能,可以借助以下方式:


1. 直接记录日志

在发送请求前后,通过日志工具(如 SLF4JLog4j)手动记录请求参数和返回值。

示例代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class RestTemplateLogger {
    private static final Logger logger = LoggerFactory.getLogger(RestTemplateLogger.class);

    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();
        String url = "https://example.com/api/users";
        User request = new User("Alice", 25);

        logger.info("Sending POST request to URL: {}, Request Body: {}", url, request);

        ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

        logger.info("Response Status: {}, Response Body: {}", response.getStatusCode(), response.getBody());
    }
}

优点:

  • 简单易懂,直接使用日志工具记录。

缺点:

  • 手动记录日志可能会导致重复代码,尤其是在多个接口中重复使用。

2. 使用 ClientHttpRequestInterceptor 统一拦截请求和响应

RestTemplate 添加拦截器,可以统一记录所有请求的日志。

实现步骤

  1. 创建拦截器

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpRequest;
    import org.springframework.http.client.ClientHttpRequestExecution;
    import org.springframework.http.client.ClientHttpRequestInterceptor;
    import org.springframework.http.client.ClientHttpResponse;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.nio.charset.StandardCharsets;
    
    public class LoggingInterceptor implements ClientHttpRequestInterceptor {
        private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
    
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            // 记录请求
            logRequest(request, body);
    
            // 执行请求
            ClientHttpResponse response = execution.execute(request, body);
    
            // 记录响应
            logResponse(response);
    
            return response;
        }
    
        private void logRequest(HttpRequest request, byte[] body) {
            logger.info("Request URI: {}", request.getURI());
            logger.info("Request Method: {}", request.getMethod());
            logger.info("Request Headers: {}", request.getHeaders());
            logger.info("Request Body: {}", new String(body, StandardCharsets.UTF_8));
        }
    
        private void logResponse(ClientHttpResponse response) throws IOException {
            StringBuilder responseBody = new StringBuilder();
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    responseBody.append(line);
                }
            }
    
            logger.info("Response Status Code: {}", response.getStatusCode());
            logger.info("Response Headers: {}", response.getHeaders());
            logger.info("Response Body: {}", responseBody.toString());
        }
    }
    
  2. 注册拦截器到 RestTemplate

    import org.springframework.http.client.ClientHttpRequestInterceptor;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class RestTemplateConfig {
        public static RestTemplate createLoggingRestTemplate() {
            RestTemplate restTemplate = new RestTemplate();
    
            // 添加自定义拦截器
            List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(restTemplate.getInterceptors());
            interceptors.add(new LoggingInterceptor());
            restTemplate.setInterceptors(interceptors);
    
            return restTemplate;
        }
    }
    
  3. 使用带有拦截器的 RestTemplate

    RestTemplate restTemplate = RestTemplateConfig.createLoggingRestTemplate();
    String url = "https://example.com/api/users";
    User user = new User("Alice", 25);
    
    ResponseEntity<String> response = restTemplate.postForEntity(url, user, String.class);
    

优点:

  • 所有请求和响应的日志记录逻辑集中在拦截器中,无需重复编写日志代码。
  • 易于扩展,例如添加性能监控。

缺点:

  • 如果响应体较大,可能影响性能。

3. 格式化日志输出

为了更清晰地阅读日志,可以格式化输出 JSON 内容。借助工具库(如 Jackson)格式化 JSON 是一种常见做法。

格式化日志的拦截器改进

import com.fasterxml.jackson.databind.ObjectMapper;

public class LoggingInterceptor implements ClientHttpRequestInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        logRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        logResponse(response);
        return response;
    }

    private void logRequest(HttpRequest request, byte[] body) {
        try {
            logger.info("Request URI: {}", request.getURI());
            logger.info("Request Method: {}", request.getMethod());
            logger.info("Request Headers: {}", objectMapper.writeValueAsString(request.getHeaders()));
            logger.info("Request Body: {}", objectMapper.readTree(body));
        } catch (Exception e) {
            logger.error("Error logging request", e);
        }
    }

    private void logResponse(ClientHttpResponse response) throws IOException {
        StringBuilder responseBody = new StringBuilder();
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                responseBody.append(line);
            }
        }

        try {
            logger.info("Response Status Code: {}", response.getStatusCode());
            logger.info("Response Headers: {}", objectMapper.writeValueAsString(response.getHeaders()));
            logger.info("Response Body: {}", objectMapper.readTree(responseBody.toString()));
        } catch (Exception e) {
            logger.error("Error logging response", e);
        }
    }
}

改进效果:

  • 请求体和响应体会以格式化的 JSON 输出,方便调试和查看。

4. 敏感数据的处理

在记录日志时,避免将敏感数据(如密码、令牌)直接输出。可以对敏感字段进行屏蔽或模糊处理。

示例

在拦截器中对敏感字段进行屏蔽:

private String maskSensitiveData(String body) {
    // 简单屏蔽逻辑(可扩展为更复杂的处理)
    return body.replaceAll("\"password\":\".*?\"", "\"password\":\"***\"");
}

在日志记录前调用:

logger.info("Request Body (masked): {}", maskSensitiveData(new String(body, StandardCharsets.UTF_8)));

5. 结合日志链路追踪

可以结合分布式系统中的链路追踪工具(如 Spring SleuthZipkin),记录每个请求的唯一追踪 ID,便于在微服务环境中追踪请求路径。

示例

使用 org.springframework.cloud.sleuth 自动添加追踪 ID 到日志中,无需手动处理。


总结

记录 RestTemplate 请求参数和返回值到日志,可以结合以下方法:

  1. 直接记录日志: 适用于简单场景。
  2. 使用拦截器: 集中日志逻辑,适合复杂场景。
  3. 格式化日志: 提升可读性。
  4. 处理敏感数据: 避免暴露机密信息。
  5. 结合链路追踪: 在分布式系统中更有效地调试和监控。

通过合理的日志设计,不仅可以帮助调试问题,还能提高系统的透明度和可维护性。

posted @ 2024-11-19 01:17  gongchengship  阅读(4)  评论(0)    收藏  举报