使用restTemlate 时,如何优雅而全面地记录请求参数和返回值到日志
使用 RestTemplate 时,记录请求参数和返回值到日志是调试和维护的重要环节。为了优雅而全面地实现这一功能,可以借助以下方式:
1. 直接记录日志
在发送请求前后,通过日志工具(如 SLF4J 或 Log4j)手动记录请求参数和返回值。
示例代码
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 添加拦截器,可以统一记录所有请求的日志。
实现步骤
- 
创建拦截器:
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()); } } - 
注册拦截器到
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; } } - 
使用带有拦截器的
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 Sleuth 和 Zipkin),记录每个请求的唯一追踪 ID,便于在微服务环境中追踪请求路径。
示例
使用 org.springframework.cloud.sleuth 自动添加追踪 ID 到日志中,无需手动处理。
总结
记录 RestTemplate 请求参数和返回值到日志,可以结合以下方法:
- 直接记录日志: 适用于简单场景。
 - 使用拦截器: 集中日志逻辑,适合复杂场景。
 - 格式化日志: 提升可读性。
 - 处理敏感数据: 避免暴露机密信息。
 - 结合链路追踪: 在分布式系统中更有效地调试和监控。
 
通过合理的日志设计,不仅可以帮助调试问题,还能提高系统的透明度和可维护性。
                    
                
                
            
        
浙公网安备 33010602011771号