《Feign日志记录方案设计》

860 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

《Feign日志记录方案设计》

1.需求背景

随着我们内部系统间交互越发紧密,Feign的使用越来越多,但目前存在着Feign方式调用日志无法保存,报错难以排查等问题。 为了解决上述问题,通过对Feign的日志功能进行重写的方式,实现了日志的格式化输出和存储。

2.实现方案

Feign默认提供了获取请求和响应报文并打印到控制台的功能,但需要进行开启,且只能打印到控制台中。为了将请求和响应信息进行存储,对Feign的功能进行了重新实现。

2.1.重新实现FeignLoggerFactory接口

通过自定义CustomizationFeignLoggerFactory来重新实现FeignLoggerFactory接口,可以将打印log的对象设置为我们新定义的对象,从而实现调用进行其他操作。

2.1.1.重写定义log对象

重写create方法,定义logger对象为

2.1.2.开启feign日志打印功能

通过设置Logger.Level为FULL,开启日志信息的获取和打印

2.1.3.代码实现
@Configuration
public class CustomizationFeignLoggerFactory implements FeignLoggerFactory {

    /**
     * 是否开启feign日志记录功能标识
     * true-是,false-否
     */
    @Value("${feignlog.flag:false}")
    private boolean openFlag;

    public CustomizationFeignLoggerFactory() {
    }

    //实现create方法 new自定义的CustomizationFeignLogger
    @Override
    public Logger create(Class<?> type) {
        return openFlag ? new CustomizationFeignLogger() : new Slf4jLogger(type);
    }

    //开启openfeign的日志,Primary用来解决bean冲突
    @Bean
    @Primary
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}
2.2.重写feign.Logger抽象类

通过自定义CustomizationFeignLogger来重写feign.Logger对象,来重写实现日志相关操作。

2.2.1.重写logRequest()方法

通过重写logRequest()方法,来获取请求信息并将请求信息进行保存。

2.2.2.重写logAndRebufferResponse()方法

通过重写logAndRebufferResponse()方法,来获取响应信息并将响应信息进行对应的更新。

2.2.3.代码实现
@Slf4j
public class CustomizationFeignLogger extends feign.Logger {

    /**
     * 用来存放logId
     */
    private static final ThreadLocal<Long> logIdCache = new ThreadLocal<>();

    private static final Long workerId = 1L;

    private static final Long dataCenterId = 1L;

    private static final String defaultValue = "result data is null";


    public CustomizationFeignLogger() {
    }

    @Override
    protected void log(String configKey, String format, Object... args) {
    }

    @Override
    protected void logRequest(String configKey, Level logLevel, Request request) {
        String method = request.method();
        String url = request.url();
        String param = (request.body() == null || request.body().length == 0) ? "" : new String(request.body(), UTF_8);

        //通过雪花算法生成主键logId
        Snowflake snowflake = new Snowflake(workerId, dataCenterId);
        long logId = snowflake.nextId();
        logIdCache.set(logId);

        //保存请求信息
        SpringUtils.getBean(FeignLoggerService.class).saveFeignLog(url, param, logId);
        log.debug("Feign请求信息为:method:{}, url:{}, param:{}", method, url, param);

    }

    //自定义响应日志
    @Override
    protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
        String result = "";
        int status = response.status();

        try {
            if (response.body() != null) {
                byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                result = decodeOrDefault(bodyData, UTF_8, defaultValue);
                return response.toBuilder().body(bodyData).build();
            }
            return response;
        } finally {
            //更新响应信息
            Long logId = logIdCache.get();
            SpringUtils.getBean(FeignLoggerService.class).updateFeignLog(result, elapsedTime, logId);
            logIdCache.remove();
            log.debug("Feign响应信息为: status:{}, result:{}, ElapsedTime:[{}ms]", status, result, elapsedTime);
        }
    }
}
3.数据库表设计

选择在dbinc的库中新增t_feign_trans_log表,来记录feign的调用日志。

3.1表设计
CREATE TABLE `t_feign_trans_log` (
  `log_id` bigint(20) NOT NULL COMMENT '日志ID(对应交易流水号transId)',
  `pre_log_id` bigint(20) NOT NULL COMMENT '上一条日志ID',
  `trans_id` bigint(200) NOT NULL COMMENT '交易接口ID:对应d_trans表',
  `tenant_id` bigint(20) NOT NULL COMMENT '商户ID:对应t_tenant表',
  `application_id` bigint(20) NOT NULL COMMENT '应用ID:对应t_application表',
  `open_id` varchar(100) DEFAULT '1' COMMENT '请求用户ID:对应cms_user表id',
  `client_ip` varchar(255) DEFAULT NULL COMMENT '客户端IP地址',
  `req_url` varchar(100) DEFAULT NULL COMMENT '请求地址',
  `trans_time` datetime DEFAULT NULL COMMENT '交易时间',
  `req_time` datetime DEFAULT NULL COMMENT '请求时间',
  `rep_time` datetime DEFAULT NULL COMMENT '响应时间',
  `cost` double DEFAULT NULL COMMENT '耗时 单位秒',
  `req_msg` mediumtext COMMENT '请求报文',
  `rep_msg` mediumtext COMMENT '响应报文',
  `state` char(30) DEFAULT NULL COMMENT '交易状态 10-成功 12-异常',
  `trans_state` char(30) DEFAULT NULL COMMENT '交易状态 10-成功 12-异常',
  `busi_state` char(30) DEFAULT NULL COMMENT '业务状态 10-成功 11 校验失败',
  `error_detail` mediumtext COMMENT '错误信息',
  `CREATE_ID` varchar(60) DEFAULT NULL COMMENT '创建人',
  `MODIFY_ID` varchar(60) DEFAULT NULL COMMENT '修改人',
  `INSERT_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '插入时间',
  `UPDATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='feign日志记录表';

4.处理流程

在这里插入图片描述

5.使用方式

5.1 修改pom.xml文件
添加以下依赖:
<!--引入feignlog依赖-->
<dependency>
    <groupId>com.micro</groupId>
    <artifactId>micro-feignlog</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
5.2 修改application-xxx.yml配置文件

在application-xxx.yml配置文件中添加以下配置:

# feign日志记录开启标识,true-开启,false-关闭
feignlog:
  flag: true

5.3 注意事项

如果添加修改yml信息或配置为false,则不会记录feign调用日志