|
|
@@ -1,152 +0,0 @@
|
|
|
-package net.yyc.common.log;
|
|
|
-
|
|
|
-import cn.hutool.core.io.IoUtil;
|
|
|
-import cn.hutool.core.net.NetUtil;
|
|
|
-import cn.hutool.core.util.CharsetUtil;
|
|
|
-import net.yyc.common.log.config.LogConfig;
|
|
|
-import net.yyc.common.log.vo.LogMessage;
|
|
|
-import feign.Client;
|
|
|
-import feign.Request;
|
|
|
-import feign.Response;
|
|
|
-import lombok.extern.slf4j.Slf4j;
|
|
|
-import org.springframework.util.CollectionUtils;
|
|
|
-
|
|
|
-import java.io.IOException;
|
|
|
-import java.util.Collection;
|
|
|
-import java.util.Map;
|
|
|
-import java.util.Objects;
|
|
|
-
|
|
|
-/**
|
|
|
- * Feign客户端调用日志输出
|
|
|
- * <p>
|
|
|
- * 由 {@link LogAutoConfiguration#feignClient} 包装为 LoadBalancerFeignClient 注册,
|
|
|
- * 保留负载均衡能力;关闭开关(log.feign.enable=false)时行为与 Client.Default 完全一致。
|
|
|
- */
|
|
|
-@Slf4j
|
|
|
-public class FeignDefaultRequestLogging extends Client.Default {
|
|
|
-
|
|
|
- /**
|
|
|
- * 请求EVENT
|
|
|
- */
|
|
|
- private static final String LOG_EVENT = "FEIGN";
|
|
|
-
|
|
|
- /**
|
|
|
- * 调用失败时 LogMessage.httpStatus 的占位状态码(无响应可取)
|
|
|
- */
|
|
|
- private static final int HTTP_STATUS_ERROR = -1;
|
|
|
-
|
|
|
- /**
|
|
|
- * 打印日志开关
|
|
|
- */
|
|
|
- private boolean enable;
|
|
|
-
|
|
|
- /**
|
|
|
- * body最大长度
|
|
|
- */
|
|
|
- private int maxLength;
|
|
|
-
|
|
|
- public FeignDefaultRequestLogging(LogConfig logConfig) {
|
|
|
- super(null, null);
|
|
|
- this.enable = logConfig.isRequestFeignEnable();
|
|
|
- this.maxLength = logConfig.getRequestMaxLength();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Response execute(Request request, Request.Options options) throws IOException {
|
|
|
- if (!enable) {
|
|
|
- return super.execute(request, options);
|
|
|
- }
|
|
|
-
|
|
|
- long startTime = System.currentTimeMillis();
|
|
|
-
|
|
|
- Response response;
|
|
|
- try {
|
|
|
- response = super.execute(request, options);
|
|
|
- }
|
|
|
- catch (IOException e) {
|
|
|
- // 连接超时/读超时等调用失败也要留痕:状态码置 -1,记录异常摘要后原样抛出
|
|
|
- LogMessage logMsg = new LogMessage();
|
|
|
- logMsg.setEvent(LOG_EVENT);
|
|
|
- logMsg.setMethod(request.httpMethod().name());
|
|
|
- logMsg.setRequestUri(request.url());
|
|
|
- logMsg.setIp(NetUtil.getLocalhostStr());
|
|
|
- logMsg.setReqHeaders(assembleHeaders(request.headers()));
|
|
|
- byte[] reqBody = request.body();
|
|
|
- if (Objects.nonNull(reqBody)) {
|
|
|
- logMsg.setReqBody(buildBodyText(reqBody));
|
|
|
- }
|
|
|
- logMsg.setHttpStatus(HTTP_STATUS_ERROR);
|
|
|
- logMsg.setRespBody("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
|
|
- logMsg.setDuration(System.currentTimeMillis() - startTime);
|
|
|
- log.info(logMsg.buildJson());
|
|
|
- throw e;
|
|
|
- }
|
|
|
-
|
|
|
- //拼接日志
|
|
|
- LogMessage logMsg = new LogMessage();
|
|
|
- logMsg.setEvent(LOG_EVENT);
|
|
|
- logMsg.setMethod(request.httpMethod().name());
|
|
|
- logMsg.setRequestUri(request.url());
|
|
|
- logMsg.setIp(NetUtil.getLocalhostStr());
|
|
|
- logMsg.setReqHeaders(assembleHeaders(request.headers()));
|
|
|
-
|
|
|
- byte[] reqBody = request.body();
|
|
|
- if (Objects.nonNull(reqBody)) {
|
|
|
- logMsg.setReqBody(buildBodyText(reqBody));
|
|
|
- }
|
|
|
- logMsg.setHttpStatus(response.status());
|
|
|
- logMsg.setRespHeaders(assembleHeaders(response.headers()));
|
|
|
-
|
|
|
- // 响应体必须完整读取并回填(业务方依赖完整 body),仅日志字符串截断;
|
|
|
- // 读取失败时流已损坏无法恢复,记录告警并继续(调用方会得到读流异常)
|
|
|
- Response.Body responseBody = response.body();
|
|
|
- if (responseBody != null) {
|
|
|
- try {
|
|
|
- byte[] respBody = readBody(responseBody);
|
|
|
- response = response.toBuilder().body(respBody).build();
|
|
|
- logMsg.setRespBody(buildBodyText(respBody));
|
|
|
- }
|
|
|
- catch (IOException e) {
|
|
|
- log.warn("FEIGN 响应体读取失败, uri: {}", request.url(), e);
|
|
|
- }
|
|
|
- }
|
|
|
- logMsg.setDuration(System.currentTimeMillis() - startTime);
|
|
|
- log.info(logMsg.buildJson());
|
|
|
- return response;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 读取响应体:Content-Length 已知时按长度一次性分配,避免 ByteArrayOutputStream 动态扩容放大内存占用
|
|
|
- */
|
|
|
- private byte[] readBody(Response.Body body) throws IOException {
|
|
|
- long length = body.length();
|
|
|
- return length > 0 && length <= Integer.MAX_VALUE
|
|
|
- ? IoUtil.readBytes(body.asInputStream(), (int) length)
|
|
|
- : IoUtil.readBytes(body.asInputStream());
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * body 转日志文本:超过 maxLength 截断并追加总长度标记,防止超大响应撑爆日志
|
|
|
- */
|
|
|
- private String buildBodyText(byte[] body) {
|
|
|
- if (body.length > maxLength) {
|
|
|
- return new String(body, 0, maxLength, CharsetUtil.CHARSET_UTF_8)
|
|
|
- + String.format("...[TRUNCATED,total=%d]", body.length);
|
|
|
- }
|
|
|
- return new String(body, CharsetUtil.CHARSET_UTF_8);
|
|
|
- }
|
|
|
-
|
|
|
- private String assembleHeaders(Map<String, Collection<String>> headers) {
|
|
|
- if (CollectionUtils.isEmpty(headers)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
-
|
|
|
- for (Map.Entry<String, Collection<String>> entrySet : headers.entrySet()) {
|
|
|
- String key = entrySet.getKey();
|
|
|
- Collection<String> values = entrySet.getValue();
|
|
|
- sb.append(key).append("=").append(String.join(";", values)).append(LogMessage.DEFAULT_LOG_FIELD_SEPARATOR_2);
|
|
|
- }
|
|
|
- return sb.substring(0, sb.length() - 1);
|
|
|
- }
|
|
|
-}
|