文章目录
- 前言
- 一、spring-rest-template-logger实战
- 1、引入依赖
- 2、配置RestTemplate
- 3、配置RestTemplate请求的日志级别
- 4、单元测试
- 5、日志打印
- 6、自定义日志打印格式
- 二、原理分析
- 三、优化改进
- 总结
前言
在项目开发过程中经常需要使用Http协议请求第三方接口,而所有针对第三方的请求都强烈推荐打印请求日志,以便问题追踪。最常见的做法是封装一个Http请求的工具类,在里面定制一些日志打印,但这种做法真的比较low,本文将分享一下在Spring Boot中如何优雅的打印Http请求日志。
一、spring-rest-template-logger实战
首先我们演示下在github上找到的通用日志打印组件spring-rest-template-logger的使用。
Github地址:Spring RestTemplate Logger
1、引入依赖
<dependency>
<groupId>org.hobsoft.spring</groupId>
<artifactId>spring-rest-template-logger</artifactId>
<version>2.0.0</version>
</dependency>
2、配置RestTemplate
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplateBuilder()
.customizers(new LoggingCustomizer())
.build();
return restTemplate;
}
}
3、配置RestTemplate请求的日志级别
logging.level.org.hobsoft.spring.resttemplatelogger.LoggingCustomizer = DEBUG
4、单元测试
@SpringBootTest
@Slf4j
class LimitApplicationTests {
@Resource
RestTemplate restTemplate;
@Test
void testHttpLog() throws Exception{
String requestUrl = "https://api.uomg.com/api/icp";
//构建json请求参数
JSONObject params = new JSONObject();
params.put("domain", "www.baidu.com");
//请求头声明请求格式为json
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
//创建请求实体
HttpEntity<String> entity = new HttpEntity<>(params.toString(), headers);
//执行post请求,并响应返回字符串
String resText = restTemplate.postForObject(requestUrl, entity, String.class);
System.out.println(resText);
}
}
5、日志打印
2023-06-25 10:43:44.780 DEBUG [Sleuth,b6ac76a169b1af33,b6ac76a169b1af33] 63501 --- [ main] o.h.s.r.LoggingCustomizer : Request: POST https://api.uomg.com/api/icp {"domain":"www.baidu.com"}
2023-06-25 10:43:45.849 DEBUG [Sleuth,b6ac76a169b1af33,b6ac76a169b1af33] 63501 --- [ main] o.h.s.r.LoggingCustomizer : Response: 200 {"code":200901,"msg":"查询域名不能为空"}
{"code":200901,"msg":"查询域名不能为空"}
可以看见,我们只是简单的向RestTemplate中配置了LoggingCustomizer,就实现了http请求的日志通用化打印,在Request和Response中分别打印出了请求的入参和响应结果。
6、自定义日志打印格式
可以通过继承LogFormatter接口实现自定义的日志打印格式。
RestTemplate restTemplate = new RestTemplateBuilder()
.customizers(new LoggingCustomizer(LogFactory.getLog(LoggingCustomizer.class), new MyLogFormatter()))
.build();
默认的日志打印格式化类DefaultLogFormatter:
public class DefaultLogFormatter implements LogFormatter {
private static final Charset DEFAULT_CHARSET;
public DefaultLogFormatter() {
}
//格式化请求
public String formatRequest(HttpRequest request, byte[] body) {
String formattedBody = this.formatBody(body, this.getCharset(request));
return String.format("Request: %s %s %s", request.getMethod(), request.getURI(), formattedBody);
}
//格式化响应
public String formatResponse(ClientHttpResponse response) throws IOException {
String formattedBody = this.formatBody(StreamUtils.copyToByteArray(response.getBody()), this.getCharset(response));
return String.format("Response: %s %s", response.getStatusCode().value(), formattedBody);
}
……
}
可以参考DefaultLogFormatter的实现,定制自定的日志打印格式化类MyLogFormatter。
二、原理分析
首先分析下LoggingCustomizer类,通过查看源码发现,LoggingCustomizer继承自RestTemplateCustomizer,RestTemplateCustomizer表示RestTemplate的定制器,RestTemplateCustomizer是一个函数接口,只有一个方法customize,用来扩展定制RestTemplate的属性。
@FunctionalInterface
public interface RestTemplateCustomizer {
void customize(RestTemplate restTemplate);
}
LoggingCustomizer的核心方法customize:
public class LoggingCustomizer implements RestTemplateCustomizer{
public void customize(RestTemplate restTemplate) {
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory()));
//向restTemplate中注入了日志拦截器
restTemplate.getInterceptors().add(new LoggingInterceptor(this.log, this.formatter));
}
}
日志拦截器中的核心方法:
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
private final Log log;
private final LogFormatter formatter;
public LoggingInterceptor(Log log, LogFormatter formatter) {
this.log = log;
this.formatter = formatter;
}
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
if (this.log.isDebugEnabled()) {
//打印http请求的入参
this.log.debug(this.formatter.formatRequest(request, body));
}
ClientHttpResponse response = execution.execute(request, body);
if (this.log.isDebugEnabled()) {
//打印http请求的响应结果
this.log.debug(this.formatter.formatResponse(response));
}
return response;
}
}
原理小结:
通过向restTemplate对象中添加自定义的日志拦截器LoggingInterceptor,使用自定义的格式化类DefaultLogFormatter来打印http请求的日志。
三、优化改进
可以借鉴spring-rest-template-logger的实现,通过Spring Boot Start自动化配置原理, 声明自动化配置类RestTemplateAutoConfiguration,自动配置RestTemplate。
这里只是提供一些思路,搞清楚相关组件的原理后,我们就可以轻松定制通用的日志打印组件。
@Configuration
public class RestTemplateAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplateBuilder()
.customizers(new LoggingCustomizer())
.build();
return restTemplate;
}
}
总结
这里的优雅打印并不是针对http请求日志打印的格式,而是通过向RestTemplate中注入拦截器实现通用性强、代码侵入性小、具有可定制性的日志打印方式。
-
在Spring Boot中http请求都推荐采用RestTemplate模板类
,而无需自定义http请求的静态工具类,比如HttpUtil - 推荐在配置类中采用@Bean将RestTemplate类配置成统一通用的单例对象注入到Spring 容器中,而不是每次使用的时候重新声明。
- 推荐采用拦截器实现http请求日志的通用化打印