[享学Feign] 十一、Feign通过feign-slf4j模块整合logback记录日志

极致无处不在,可以小到调整一行代码提交,可以大到整个研发流程。极致可以运用在技术上,也可体现在团队管理…

–> 返回专栏总目录 <–
代码下载地址: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-apilogback-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配置文件的自动发现策略,它的优先级从上自下:

  1. key为logback.configurationFile的系统属性,如-Dlogback.configurationFile=src/test/resources/logback.xml
  2. classpath下文件:
    1. logback-test.xml
    2. logback.groovy
    3. 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的基本情况。

分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了377 篇原创文章 · 获赞 572 · 访问量 51万+
展开阅读全文

logger报错找不到slf4j.logger

08-31

Caused by: java.lang.NoClassDefFoundError: Lorg/slf4j/Logger; at java.lang.Class.getDeclaredFields0(Native Method) at java.lang.Class.privateGetDeclaredFields(Class.java:2317) at java.lang.Class.getDeclaredFields(Class.java:1762) at org.apache.catalina.util.Introspection.getDeclaredFields(Introspection.java:106) at org.apache.catalina.startup.WebAnnotationSet.loadFieldsAnnotation(WebAnnotationSet.java:270) at org.apache.catalina.startup.WebAnnotationSet.loadApplicationListenerAnnotations(WebAnnotationSet.java:89) at org.apache.catalina.startup.WebAnnotationSet.loadApplicationAnnotations(WebAnnotationSet.java:63) at org.apache.catalina.startup.ContextConfig.applicationAnnotationsConfig(ContextConfig.java:417) at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:891) at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:388) at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117) at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5519) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145) ... 40 more Caused by: java.lang.ClassNotFoundException: org.slf4j.Logger at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1892) at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1735) ... 54 more 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie

分享到微信朋友圈

×

扫一扫,手机浏览