首先实现一个Interceptor器,对请求进行拦截。
/**
* Web 客户端请求日志打印过滤器
*
* @author Jiong
*/
public class TraceLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
private final static Logger log = LoggerFactory.getLogger(TraceLogClientHttpRequestInterceptor.class);
private final static long ELAPSED_TIME_OVER_TIME = 1000;
private final static String LOG_TEMPLATE = "{} {} {} {}";
@Value("${pms.web.log.printLength:500}")
private int maxPayloadLength = 500;
private final JsonConverter jsonConverter;
public TraceLogClientHttpRequestInterceptor(final JsonConverter jsonConverter) {
this.jsonConverter = jsonConverter;
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
final long start = System.currentTimeMillis();
ClientHttpResponse response = execution.execute(request, body);
final long cost = System.currentTimeMillis() - start;
final Map<String, Object> logMap = new HashMap<>(2);
final MediaType requestMediaType = request.getHeaders().getContentType();
if (MediaType.APPLICATION_JSON.isCompatibleWith(requestMediaType)
|| MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(requestMediaType)) {
final String requestBody = this.getRequestMessagePayload(body, requestMediaType);
if (StringUtils.hasText(requestBody)) {
logMap.put("requestBody", requestBody);
}
}
final MediaType responseMediaType = response.getHeaders().getContentType();
if (MediaType.APPLICATION_JSON.isCompatibleWith(responseMediaType)) {
response = new ContentCachingClientHttpResponseWrapper(response);
final String respBody = this.getResponseMessagePayload(response.getBody(), responseMediaType);
if (StringUtils.hasText(respBody)) {
logMap.put("respBody", respBody);
}
} else {
if (Objects.nonNull(responseMediaType)) {
logMap.put("respType", responseMediaType.getType());
}
logMap.put("respLen", response.getHeaders().getContentLength());
}
if (response.getStatusCode().value() >= 400) {
logMap.put("status", response.getStatusCode().value());
log.warn(LOG_TEMPLATE, cost,
request.getMethod().name(),
request.getURI(), this.jsonConverter.write(logMap));
} else {
if (cost > ELAPSED_TIME_OVER_TIME) {
log.warn(LOG_TEMPLATE, cost,
request.getMethod().name(),
request.getURI(), this.jsonConverter.write(logMap));
} else {
log.info(LOG_TEMPLATE, cost,
request.getMethod().name(),
request.getURI(), this.jsonConverter.write(logMap));
}
}
return response;
}
protected String getRequestMessagePayload(final byte[] body, final MediaType mediaType) {
if (body.length > 0) {
// final int length = Math.min(body.length, this.maxPayloadLength);
// return new String(body, 0, length, this.getCharset(mediaType));
// 转换成字符串后再截断可以防止中文乱码.
return new String(body, this.getCharset(mediaType))
.substring(0, Math.min(body.length, this.maxPayloadLength));
}
return null;
}
protected String getResponseMessagePayload(final InputStream inputStream, final MediaType mediaType) {
if (!inputStream.markSupported()) {
log.warn("InputStream mark/reset not supported");
return null;
}
final byte[] buf = new byte[this.maxPayloadLength];
int length = 0;
try {
inputStream.mark(this.maxPayloadLength);
length = inputStream.read(buf);
inputStream.reset();
} catch (final IOException ignore) {
// 忽略异常
}
if (length > 0) {
return new String(buf, 0, length, this.getCharset(mediaType));
}
return null;
}
private Charset getCharset(final MediaType mediaType) {
return Objects.isNull(mediaType.getCharset())
? StandardCharsets.UTF_8
: mediaType.getCharset();
}
private static final class ContentCachingClientHttpResponseWrapper implements ClientHttpResponse {
private final ClientHttpResponse clientHttpResponse;
private BufferedInputStream inputStream = null;
private ContentCachingClientHttpResponseWrapper(final ClientHttpResponse clientHttpResponse) {
this.clientHttpResponse = clientHttpResponse;
}
@Override
public HttpStatusCode getStatusCode() throws IOException {
return this.clientHttpResponse.getStatusCode();
}
@Deprecated(
since = "6.0"
)
@Override
public int getRawStatusCode() throws IOException {
return this.clientHttpResponse.getRawStatusCode();
}
@Override
public String getStatusText() throws IOException {
return this.clientHttpResponse.getStatusText();
}
@Override
public void close() {
if (Objects.nonNull(this.inputStream)) {
try {
this.inputStream.close();
} catch (final IOException ignored) {
// 忽略关闭异常
}
}
this.clientHttpResponse.close();
}
@Override
public synchronized InputStream getBody() throws IOException {
if (Objects.isNull(this.inputStream)) {
this.inputStream = new BufferedInputStream(this.clientHttpResponse.getBody());
}
return this.inputStream;
}
@Override
public HttpHeaders getHeaders() {
return this.clientHttpResponse.getHeaders();
}
}
}
然后对 RestTemplateBuilder 的默认值进行配置。
@Bean
@Lazy
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(final RestTemplateAutoConfiguration restTemplateAutoConfiguration, final RestTemplateBuilderConfigurer restTemplateBuilderConfigurer, final JsonConverter jsonConverter) {
return restTemplateAutoConfiguration.restTemplateBuilder(restTemplateBuilderConfigurer)
.setConnectTimeout(Duration.ofSeconds(15))
.setReadTimeout(Duration.ofSeconds(15))
.additionalInterceptors(new TraceLogClientHttpRequestInterceptor(jsonConverter));
}