极致无处不在,可以小到调整一行代码提交,可以大到整个研发流程。极致可以运用在技术上,也可体现在团队管理…
–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/feign-learning
前言
作为一个程序员,说到日志的重要性,怎么强调都不过分。然而每个流行框架都有它内置使用的日志库,比如:Spring使用commons-logging
(这是Spring的唯一强外部依赖,其它的依赖均非强制)记录日志。
Feign它自己提供了一个日志抽象feign.Logger
用于记录日志,它并不限于具体底层实现。它内部提供了基于java.util.logging.Logger
以及System.err
的基础实现,那么很显然生产上不可能使用它们来实现日志打印。
本文将介绍Feign和第三方日志框架的集成使用,利用它提供的feign-slf4j
便可轻松完成整合,并且使用当下最流行的logback作为示例进行讲解。
正文
关于第三方日志,Java有个标准的日志门面:slf4,它似乎已经在现行的日志标准。
所以对于第三方日志的集成,Feign也拥抱了slf4j,选择了面向抽象集成,具体底层日志实现框架交由使用者自行裁定。
feign-slf4j
Feign非常暖心的提供了feign-slf4j
这个模块,方便使用者便捷的完成日志输出。
GAV如下:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>${feign.version}</version>
</dependency>
该模块会携带依赖包:slf4j-api
。
说明:仅仅只是“携带”的API,并不带有任何
slf4j
的底层实现。具体实现还需使用者手动导入
源码分析
feign-slf4j
这个jar内有且仅有一个类:Slf4jLogger
,它是feign.Logger
接口的一个实现类。
public class Slf4jLogger extends feign.Logger {
// 这个Logger是org.slf4j.Logger
private final Logger logger;
// 构造器
public Slf4jLogger() {
this(feign.Logger.class);
}
public Slf4jLogger(Class<?> clazz) {
this(LoggerFactory.getLogger(clazz));
}
public Slf4jLogger(String name) {
this(LoggerFactory.getLogger(name));
}
// 请注意这个构造器是default的访问权限,并不能由你手动指定底层日志实现
// 而是由LoggerFactory.getLogger(clazz)来获取...
// 所以和你集成的情况强相关...
Slf4jLogger(Logger logger) {
this.logger = logger;
}
// 实现唯一抽象方法:只输出debug级别以上的日志。前提是底层日志框架开启了debug级别
// 若底层开启的info级别,那这里是不会输出的哦~
@Override
protected void log(String configKey, String format, Object... args) {
if (logger.isDebugEnabled()) {
logger.debug(String.format(methodTag(configKey) + format, args));
}
}
}
如果底层日志记录器启用了debug
日志记录,则会debug级别级以上的日志记录到SLF4J。至于底层日志框架到底用哪个,可在构造的时候传入。
说明:也就是说,默认情况下只有底层日志记录器开启了
debug
级别,才会予以记录,否则直接忽略。
整合logback
Feign整合logback,实际上就是slf4j整合logback。
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
logback和著名的日志框架log4j
出自同一人之手,作为新时代日志框架的翘楚,它不仅被Spring Boot
官方推荐,也是众多其它框架的首选日志实现。
因此本文以logback为例,让它作为slf4j的底层实现,完成和Feign的整合,帮Feign
记录日志。
说明:这是Feign面向日志门面(面向接口编程)的体现,与其说是Feign整合第三方日志框架,倒不如说是SLF4J整合其它日志框架。
因本文并不是专业介绍日志框架的文章,所以很多知识点的介绍点到即止。
logback简单介绍
主要介绍logback的三个模块的作用:
logback-core
:提供核心功能,是下面2个部分的基础。- 注意:core部分并不依赖于slf4j-api部分
logback-classic
:实现了Slf4j的API,所以当想配合Slf4j使用时,需要引入logback-classic。- 它会依赖于slf4j-api部分,所以可以说是天然支持
logback-access
:为了集成Servlet环境而准备的,可提供HTTP-access的日志接口。- 一般情况下都用不到(但有研究价值)
使用示例
在POM文件里,按照上面GAV,导入logback-classic
这个Jar(包含slf4j-api
和logback-core
),这样就简单的直接生效了。示例如下:
@Test
public void fun1(){
Logger logger = LoggerFactory.getLogger("【test name一般显示全类名】");
System.out.println(logger.getClass() + "\n");
logger.trace("trace");
logger.debug("debug");
logger.info("info");
logger.warn("warn");
logger.error("error");
}
运行程序,打印结果如下截图:
这里有个小细节:trace()
日志并没有输出,这是因为logback默认日志级别是debug级别。
当然,若你没有导入logback的相关jar,再次运行此程序,控制台打印如下:
class org.slf4j.helpers.NOPLogger
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
“报错”说找不到org.slf4j.impl.StaticLoggerBinder
该实现类,因为这是交给日志底层实现来做的,若你没提供“报错”喽,所以下面的logger.debug/info()
等等不会有任何内容输出~
说明:此处指的“报错”并不是真的抛出异常了,它并不阻断程序的运行,只是让你的日志记录都失效而已,不会对你正常功能有任何影响(就以标准错误流输出而已~)
不难发现,slf4j和logback的整个毫无难度,仅仅只需导入底层实现框架的jar便自动生效了。
“自动生效”原因解释
为了满足一下小伙伴们的好奇心,此处稍微解释一下“自动生效”的原因。一切都可从LoggerFactory.getLogger()
这个方法开始:
1、getLogger()方法:
LoggerFactory:
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
2、getILoggerFactory()获取工厂实例方法
LoggerFactory:
public static ILoggerFactory getILoggerFactory() {
// 执行初始化动作(该动作只会执行一次)
// 所以即使你执行多次getLogger也只会执行一次
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
// 最主要的是这个方法:初始化
performInitialization();
}
// 根据初始化状态判断
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
return TEMP_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
3、performInitialization();初始化
LoggerFactory:
private final static void performInitialization() {
bind(); // 绑定:绑定具体底层实现
// 初始化成功的一个校验,暂可忽略不管
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
4、bind();绑定实际底层实现
LoggerFactory:
private final static void bind() {
try {
// 从Classpath里找到所有的org/slf4j/impl/StaticLoggerBinder.class
// 注意:可能有多个,毕竟也可能存在多个实现
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
...
// 实现真正的绑定 把实现绑定在slf4j
// 请注意:该API不在slf4j-api里,而是在logback-classic里~
StaticLoggerBinder.getSingleton();
...
} catch (NoClassDefFoundError ncde) {
...
} catch (java.lang.NoSuchMethodError nsme) {
...
} catch (Exception e) { ... }
}
4、StaticLoggerBinder.getSingleton()
完成初始化动作(logback底层实现的初始化)
StaticLoggerBinder:
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
// 执行一系列logback的初始化动作
static {
SINGLETON.init();
}
这就是自动绑定的步骤。而logback确实有这个API:
说明:不吹不黑,slf4j-api这种SPI方式也挺有意思的:完全通过路径名类加载 + 异常捕获方式来确定具体实现,完成自动绑定。
自动绑定配置文件
很明显,每种日志框架都有它自己专属的配置文件,以及自己能识别的位置和文件们,这属于底层实现专属,和slf4j无关。
从上面原理处知道,logback的初始化工作从StaticLoggerBinder#init
开始,然而自动寻找、绑定配置文件流程,下面很简要的展示出来:
StaticLoggerBinder:
void init() {
...
new ContextInitializer(defaultLoggerContext).autoConfig();
...
}
ContextInitializer:
public void autoConfig() throws JoranException {
...
URL url = findURLOfDefaultConfigurationFile(true);
...
}
ContextInitializer:
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
...
// 从系统属性中获取:System.getProperty(),如:
// logback.configurationFile=src/test/resources/logback.xml
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
}
...
// logback-test.xml
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
// logback.groovy
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
// logback.xml
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
这就是logback配置文件的自动发现策略,它的优先级从上自下:
- key为
logback.configurationFile
的系统属性,如-Dlogback.configurationFile=src/test/resources/logback.xml
- classpath下文件:
- logback-test.xml
- logback.groovy
- logback.xml
注意一点:这些文件是互斥的,并不会像Spring
那样起到互补效果哦~
Feign使用logback示例
有了以上理论、示例支撑,让Feign享受logback带来的效用就极其简单了。
仅需导入feign-slf4j
模块jar包,以及logback-classic
这个jar包,即完成了集成可以正常使用喽:
20:35:10.528 [main] DEBUG feign.Logger - [DemoClient#getDemo1] ---> GET http://localhost:8080/feign/demo1?name=YourBatman HTTP/1.1
20:35:10.554 [main] DEBUG feign.Logger - [DemoClient#getDemo1] ---> END HTTP (0-byte body)
...
一切正常,可以看到debug信息都输出了。
说明:请务必注意你底层实现的日志级别必须是debug级别,才会有日志输出的,否则一句话都木有,而logback默认日志级别刚好是debug。
若你使用的底层日志框架默认不是debug级别,你需要手动控制输出(一般需要配合配置文件进行修改、控制~)
总结
关于Feign整合第三方日志框架就介绍到这了,希望本文能帮助到你了解Feign的日志机制、slf4j-api、以及了解到logback的基本情况。
声明
原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭
。你也可左边扫码加入我的 Java高工、架构师 系列群大家庭学习和交流。
- [享学Feign] 一、原生Feign初体验,Netflix Feign还是Open Feign?
- [享学Feign] 二、原生Feign的注解介绍及使用示例
- [享学Feign] 三、原生Feign的核心API详解(一):UriTemplate、HardCodedTarget…
- [享学Feign] 四、原生Feign的核心API详解(二):Contract、SynchronousMethodHandler…
- [享学Feign] 五、原生Feign的编码器Encoder、QueryMapEncoder
- [享学Feign] 六、原生Feign的解码器Decoder、ErrorDecoder
- [享学Feign] 七、请求模版对象RequestTemplate和标准请求对象feign.Request
- [享学Feign] 八、Feign是如何生成接口代理对象的?Feign实例的构建器Feign.Builder详解
- [享学Feign] 九、Feign + OkHttp和Feign + Apache HttpClient哪个更香?
- [享学Feign] 十、Feign通过feign-jackson模块天然支持POJO的编码和解码