需求如下:
1,项目启动时打印项目中使用feignclient的name及url相关信息
2,在调用feignclient方法时,打印request, response信息,并有开关来控制此项功能,因为并不是所有feignclient都需要打印request, response,所以颗粒度需要细化到具体的feignclient
实现方案:
需求1:
方案1: 此种方式只能打印项目中注入的feignclient信息
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.openfeign.FeignClientSpecification;
import org.springframework.stereotype.Component;import java.lang.reflect.Proxy;
import java.util.Set;@Component
@Slf4j
public class FeignClientInfoPrinter implements BeanPostProcessor {@Autowiredprotected Set<FeignClientSpecification> feignClientSpecificationList;@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof Proxy && feignClientSpecificationList.stream().anyMatch(x -> x.getClassName().equals(beanName))){log.info("Information about feign client: {}", bean);}return bean;}
}// 打印示例如下:
// Information about feign client: HardCodedTarget(type=XXServiceClient, name=XXClient, url=https://xxxx/)
方案2: 此种方式可以打印项目中所有的feignclient信息
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClientFactoryBean;import javax.annotation.PostConstruct;
import java.util.Set;@Component
@Slf4j
public class FeignClientInfoPrinter {@Autowiredprotected Set<FeignClientFactoryBean> feignClientFactoryBeans;@PostConstructpublic void init() {feignClientFactoryBeans.forEach(x -> log.info("Information about feign client: {}, {}, {}", x.getObjectType(), x.getName(), x.getUrl()));}
}
需求2:
首先我们需要配置两个开关,
feign.third-party-logger:true #此开关开启代表允许此功能启用 feign.third-party-name:XXServiceClient, OOServiceClient... #此开关用来定义哪些feignclient类允许调用方法时打印请求响应体
代码如下:
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class FeignConfiguration {@Bean@ConditionalOnProperty(value = "feign.third-party-logger", havingValue = "true")FeignThirdPartyLogger createMyLogger() {return new FeignThirdPartyLogger();}}
import feign.Logger;
import feign.Request;
import feign.Response;
import feign.Util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;import static feign.Util.decodeOrDefault;
import static feign.form.util.CharsetUtil.UTF_8;@Slf4j
public class FeignThirdPartyLogger extends Logger {@Value("#{'${feign.third-party-name}'.split(',')}")List<String> feignThirdName;@Overrideprotected void logRequest(String configKey, Level logLevel, Request request) {if(!CollectionUtils.isEmpty(feignThirdName) && feignThirdName.contains(configKey.split("#")[0])){byte[] arrBody = request.body();String body = arrBody == null ? "" : new String(arrBody);log.info("[feign log request started]\n{} Request URL: {}\nRequest Body:\n{}",request.httpMethod(),request.url(),body);}}@Overrideprotected Response logAndRebufferResponse(String configKey,Level logLevel,Response response,long elapsedTime) {if(!CollectionUtils.isEmpty(feignThirdName) && feignThirdName.contains(configKey.split("#")[0])){int status = response.status();String content = "";if (response.body() != null && !(status == 204 || status == 205)) {byte[] bodyData;try {bodyData = Util.toByteArray(response.body().asInputStream());} catch (IOException e) {throw new RuntimeException(e);}if (bodyData.length > 0) {content = decodeOrDefault(bodyData, UTF_8, "Binary data");}response = response.toBuilder().body(bodyData).build();}log.info("[feign log request ended]\ncost time(ms): {} status:{} from {} {}\nResponse Body:\n{}",elapsedTime,status,response.request().httpMethod(),response.request().url(),content);return response;}else{return response;}}@Overrideprotected void log(String configKey, String format, Object... args) {}// 该方法可以打印header中的信息private static String CombineHeaders(Map<String, Collection<String>> headers) {StringBuilder sb = new StringBuilder();if (headers != null && !headers.isEmpty()) {sb.append("Headers:\r\n");for (Map.Entry<String, Collection<String>> ob : headers.entrySet()) {for (String val : ob.getValue()) {sb.append(" ").append(ob.getKey()).append(": ").append(val).append("\r\n");}}}return sb.toString();}
}
部分代码参考于:全局记录Feign的请求和响应日志_feign 日志-CSDN博客