【OpenFeign使用】Feign Logger 的扩展

428 阅读3分钟

【OpenFeign使用】Feign Logger 的扩展

背景

我们在日常查看日志中,对于feign调用的日志链路一般实现是两种方式: aop 或者 feign原生的日志能力

本文介绍了feign原生的日志能力

Feign的日志开启

开启feign的日志总共有两步:

  • 开启slf4j中debug 的日志级别

  • 开启 feign-client 的日志等级

    feign:
      httpclient:
        enabled: true
      client:
        config:
          default:  # 这个key值可以是你指定的feign client名称, 这里配置为default,意思是默认的日志等级策略,相当于全局的策略
            logger-level: BASIC  # 设置日志等级
    logging:
      level:
        com.**.client: debug  # 该行的key为你要输出的包路径
    

Feign 四种日志级别的对比

以下级别从低到高输出内容 feign-logger-level.png

日志等级输出内容
NONE什么都不输出
BASIC输出请求方法,url 以及响应码
HEADERS输出请求头
FULL输出请求体以及响应体

Feign 原生日志的优缺点

我们可以不修改代码很快的实现了日志的输出,在排查问题的时候可以很快的查看到日志;

但是看到这里的时候,我们可以发现原生日志的缺点:

  • 输出内容凌乱,需要我们自己去整合日志内容,才能查看到完整的日志链路
  • 输出太多的无用信息,让整个日志被无效信息占满,干扰了我们对日志的定位

对于这种情况我们可以怎么去解决呢?接下来我们可以通过查看Feign 对于整个日志框架的设计进行查看

Feign 对于日志的处理

最主要类:

Logger (feign.Logger) 主要该类是在feign包下面,要区别开log4j和logback 下面的Logger

FeignLoggerFactory: 该类主要控制了Logger的创建,会为每一个feign client 创建出对应的logger

org.springframework.cloud.openfeign.FeignClientFactoryBean#feign 在这个方法中会创建对应的Logger到feign中

FeignClientFactoryBean-feign.png

我们在阅读源码的时候发现 feign.SynchronousMethodHandler#executeAndDecode 以及feign.AsyncResponseHandler#handleResponse时可以发现,feign原生对于日志的支持能力

AsyncResponseHandler-handleResponse.png

SynchronousMethodHandler-executeAndDecode.png

我们不难发现,当日志级别不为NONE时,我们可以输出request,以及response, (但是这里有一个弊端,request和response是分开的,打印日志时不能进行汇总在一起进行打印 , 真想打印在一起的话,可以尝试去扩展下 SynchronousMethodHandler 中的方法)

实现我们自己的日志记录

1.1 声明一个logger factory

import feign.Logger;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignLoggerFactory;

@Slf4j
public class CustomFeignLoggerFactory implements FeignLoggerFactory {
    @Override
    public Logger create(Class<?> type) {
        log.info("create [{}] logger", type.getSimpleName());
        return new FeignLogger(type);
    }
}

1.2 声明自己的日志器

import feign.Request;
import feign.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

public class FeignLogger extends feign.Logger {
    private final Logger logger;

    public FeignLogger(Class<?> clazz) {
        this(LoggerFactory.getLogger(clazz));
    }

    FeignLogger(Logger logger) {
        this.logger = logger;
    }

    @Override
    protected void logRequest(String configKey, Level logLevel, Request request) {
        // 我们可以在这里扩展请求相关的日志内容
        logger.debug("我是自己在这里实现的请求日志");
        switch (logLevel) {
            case FULL:
            case HEADERS:
            case BASIC:
                log(configKey, "user custom log content: [%s]", logLevel);
                break;
            case NONE:
                logger.info("none");
                break;
            default:
                super.logRequest(configKey, logLevel, request);
                break;
        }
    }

    @Override
    protected Response logAndRebufferResponse(String configKey, Level logLevel,
                                              Response response, long elapsedTime) throws IOException {
        // 可以在这里输出我们想要的响应内容
        logger.debug("我是自己在这里实现的响应日志");
        return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
    }

    @Override
    protected void log(String configKey, String format, Object... args) {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format(methodTag(configKey) + format, args));
        }
    }
}

1.3 注入我们的日志工厂

import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignLoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableFeignClients
public class FeignConfig {

    @Bean
    public FeignLoggerFactory feignLoggerFactory() {
        return new CustomFeignLoggerFactory();
    }
}

1.4 测试我们的日志输出

feign-log-test.png

至此,我们就实现了自己的Feign基于原生的日志输出