Feign自定义打印请求响应log

发布时间:2024年01月17日

需求如下:

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 {

    @Autowired
    protected Set<FeignClientSpecification> feignClientSpecificationList;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public 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 {

    @Autowired
    protected Set<FeignClientFactoryBean> feignClientFactoryBeans;

    @PostConstruct
    public 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;

    @Override
    protected 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);
        }
    }

    @Override
    protected 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;
        }
    }

    @Override
    protected 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博客

文章来源:https://blog.csdn.net/qq_43137849/article/details/135642412
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。