使用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
请求参数和返回值到日志,可以结合以下方法:
- 直接记录日志: 适用于简单场景。
- 使用拦截器: 集中日志逻辑,适合复杂场景。
- 格式化日志: 提升可读性。
- 处理敏感数据: 避免暴露机密信息。
- 结合链路追踪: 在分布式系统中更有效地调试和监控。
通过合理的日志设计,不仅可以帮助调试问题,还能提高系统的透明度和可维护性。