在文章开始之前,先抛出几个问题:
- Java中有哪些常用的日志框架?
- 日志接口框架和日志实现框架的关系是什么?SL4J?Logback?等等?
- 网上说的日志桥接器是什么?桥接器如何实现的?各种乱七八糟的日志框架怎么混合使用?
- 项目中使用日志的正确方式?
- 如何动态修改日志级别?
在此之前,我对这些问题其实是有些傻傻分不清楚的。最近了解了一下相关的内容,因此总结一下。
常用日志框架
日常使用的日志框架一般分为两类: 日志接口框架 和 日志实现框架
日志接口框架: 可以理解成这类日志框架只是定义了一些日志操作的API,并不负责日志具体实现日志实现框架:负责日志的具体实现,比如:将日志写到文件、输出到控制台 等等
比如在日常项目中,我们通过 org.slf4j.Logger.info(xx) API将日志写到 /home/logs/info.log文件。SL4J就是日志接口框架;日志被写到文件这就是一种日志实现。
那是谁实现的呢?SL4J吗?不是的。SL4J只是负责定义写日志的API,具体实现依赖其它的日志实现框架,比如: logback、log4j 等等
日志接口
常见的日志接口框架有:
- JCL:之前叫
Jakarta Commons Logging,后更名为Commons Logging - SL4J:
Simple Logging Facade for Java
对JCL不太了解,项目上基本都在用SL4J,所以针对日志接口框架,本文主要介绍SL4J
日志实现
常见的日志实现框架有:
- JUL:
Java Util Logging,JDK自带 - log4j1.x
- log4j2.x
- logback
Sl4J
Sl4J的实现比较简单,所以简单分析一下。从下面的图也可以看出,Sl4J的类非常少
使用方式
private Logger logger = LoggerFactory.getLogger(Test.class);
private Logger logger = LoggerFactory.getLogger("loggerName");
- 使用方式有两种: 一种是通过Class获取Logger; 一种是通过字符串获取Logger
- 不管使用哪种方式获取Logger,最终都是通过字符串来获取Logger。如果传入的是Class,则会通过class.gettName来获取Logger
- 在日志框架中,维护了
loggerName -> Logger的缓存
实现原理
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
return logger;
}
public static Logger getLogger(String name) {
// 在这个步骤中,完成SL4J相关的初始化,并返回日志实现对应的ILoggerFactory
ILoggerFactory iLoggerFactory = getILoggerFactory();
// 通过具体日志是实现返回对应的 Logger
return iLoggerFactory.getLogger(name);
}
- 先找到当前应用中的
org.slf4j.impl.StaticLoggerBinder,org.slf4j.impl.StaticLoggerBinder并不由SL4J提供,而是由日志实现框架提供,比如:logback、log4j等,如果当前应用中存在多个日志实现框架,在会打印WARN日志并选择其中一个作为日志实现 - 执行
StaticLoggerBinder#getSingleton()方法,这个步骤一般会完成日志实现框架的初始 - 返回一个可用的
ILoggerFactory实现,以logback为例,返回的是LoggerContext - 通过返回的
ILoggerFactory实现获取对应的Logger,以logback为例,则通过LoggerContext对象获取Logger
通过以上的介绍可以看出,虽然我们使用的是SL4JAPI,但最终返回的确是日志具体实现的Logger
有何优点?
为什么要在应用中使用SL4J呢?这样做有什么优点吗?绕来绕去不就是返回一个logback的实现吗?那我为什么不直接使用logback?
我们先思考一下以下问题:
- 如果刚开始我们的应用中使用的是
logback,后面想使用lo4j,该怎么处理? - 如果我们应用中使用的是
logback,但依赖的一些二方包里面使用的是log4j,此时应用该如何处理日志?
可以发现,如果没有日志接口框架,非常不好弄,脑壳痛。那SL4J是如何解决这些问题的呢?
- 针对问题1: 这个非常好处理,还是保持面向
SL4JAPI编程,将日志实现框架由logback换成lo4j就可以了 - 针对问题2:我们需要用到
桥接器。首先保证应用中引入sl4j;其次保证应用中只引入一种日志是实现,比如目前应用中有logback和lo4j两种实现,我们只要引logback,同时将lo4j排除;我们将lo4j排除掉了,但应用中的一些类用到了lo4j的API,这样肯定会报错啊,所以此时需要引入log4j-over-slf4j这个桥接器。log4j-over-slf4j干嘛用的呢?即将lo4j->slf4j的转换,log4j-over-slf4j保持和lo4j的API一致(包名、类名、方法等),但是在实现类里面却是调用slf4j的API,slf4j最后又会转成logback
使用推荐
- 在应用中通过
日志接口框架 API打印日志,不要通过日志实现框架 API打印日志,优缺点上面分析过了 - 在应用中只使用一种日志实现,具体选用哪种日志实现就不说了,每个公司的标准应该都不一样,这个不重要。目前目前流行的应该是
logback和log4j2
动态日志级别
以logback为例子,动态调整日志级别有以下几种方式
-
通过在
logback.xml配置文件中添加动态扫描属性:<configuration scan="true" scanPeriod="30 seconds"></configuration>- scan:当scan被设置为true时,当配置文件发生改变,将会被重新加载,默认为true
- scanPeriod:检测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认为毫秒,当scan=true时这个值生效,默认时间间隔为1分钟 但我觉得这种方式不太合适。 改配置文件不太好改,现在大部分都是springboot应用,配置文件一般都是打包在jar文件中,不好操作。还得上服务器,容易出错,太麻烦了
-
通过
spring-boot-starter-actuator提供的REST API调整日志级别使用上不太友好, 虽然提供了
REST API,但该怎么访问呢?还得上服务器,自己想好各种参数,然后调用对应的接口,使用起来还是比较麻烦 -
spring-boot-starter-actuator结合springboot-admin通过管控台操作,方便多了。如果公司使用了springboot-admin,感觉可以用这种方式
-
自定义API
这种方式有个好处就是比较灵活,其实原理和
spring-boot-starter-actuator类似,spring-boot-starter-actuator提供的是 REST接口,而通过自定义是实现,可以通过一些其它的方式来改变日志级别,比如:置中心、或者自己实现的管控台
以自定义API实现为例,分析一下我们可以通过哪些方式来改成日志级别
- 配置中心:比如
Apollo。如果业务方已经接入了Apollo,使用这种方式还是比较推荐的。首先业务方几乎没有接入成本,只要在应用中引入你提供的动态日志二方包就可以了。其次,有关于操作权限问题,这一块Apollo都已经实现了,这一块不需要自己去实现。但这种方式也有一个缺点:无法展示目前应用中已有的Loggers,其实我感觉也算不上什么缺点,开发绝大部分时候都只关注自己定义的Logger - 实现一套日志管平台:
客户端SDK、应用Logger展示、指令派发整个一套全部自己实现,有点类似于spring-boot-starter-actuator结合springboot-admin了。如果目前已经有一个日志管控平台了,感觉可以在上面做。如果仅仅是为了动态修改日志级别而实现这么一套东西,感觉没必要