spring5新特性日志体系

spring5新特性日志体系

主流的log技术名词
1.jul

java自带的一个日志记录的技术,直接使用
java.util.logging.Logger

2.log4j
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ini复制代码//log4j依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

//log4配置文件
log4j.rootLogger=info, stdout
#mybatis的sql级别(结果的日志级别为TRACE,SQL 语句的日志级别为DEBUG)
log4j.logger.com.log.dao=TRACE
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

//log4j测试类
public static void main(String[] args) {
Logger log4j = Logger.getLogger("log4j");
log4j.info("log4j");
}

//输出
2019-09-03 11:17:06,273 INFO [log4j] - log4j

log4j特点:
可以不需要依赖第三方的技术,直接记录日志。

3.jcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typescript复制代码//jcl依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

//jcl测试类
public static void main(String[] args) {
Log jcl = LogFactory.getLog("jcl");
jcl.info("jcl");
}

//输出
(1):2019-09-03 11:23:54,470 INFO [jcl] - jcl

(2):九月 03, 2019 11:24:43 上午 jcl main
信息: jcl

这个jcl什么情况下会有不同的输出?
1.当项目有logj4的依赖的时候,就会输出(1)信息。
2.当项目没有log4j依赖的时候,就会使用java自带的日志技术jul输出(2)信息。

  • jcl源码分析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
ini复制代码#LogFactory
public static Log getLog(String name) throws LogConfigurationException {
//通过Factory获取instance实例
return getFactory().getInstance(name);
}

#LogFactoryImpl
public Log getInstance(String name) throws LogConfigurationException {
//一开始没有缓存,所以为null
Log instance = (Log) instances.get(name);
if (instance == null) {
//这里是重点,创建logger实例
instance = newInstance(name);
//放入到缓存中
instances.put(name, instance);
}
return instance;
}

protected Log newInstance(String name) throws LogConfigurationException {
Log instance;
try {
if (logConstructor == null) {
//重要代码,发现log实现类
instance = discoverLogImplementation(name);
}
else {
Object params[] = { name };
instance = (Log) logConstructor.newInstance(params);
}
if (logMethod != null) {
Object params[] = { this };
logMethod.invoke(instance, params);
}
return instance;
}
}

private Log discoverLogImplementation(String logCategory)
throws LogConfigurationException {
if (isDiagnosticsEnabled()) {
logDiagnostic("Discovering a Log implementation...");
}
initConfiguration();

//需要返回的对象
Log result = null;

//查看用户是否指定要使用的日志实现
String specifiedLogClassName = findUserSpecifiedLogClassName();
if (specifiedLogClassName != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("");
}
result = createLogFromClass(specifiedLogClassName, logCategory, true);
if (result == null) {
StringBuffer messageBuffer = new StringBuffer("User-specified log class '");
messageBuffer.append(specifiedLogClassName);
messageBuffer.append("' cannot be found or is not useable.");

// Mistyping or misspelling names is a common fault.
// Construct a good error message, if we can
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);
throw new LogConfigurationException(messageBuffer.toString());
}
return result;
}
if (isDiagnosticsEnabled()) {
logDiagnostic("");
}
//如果用户没有指定日志实现类,jcl使用默认的实现类(4个),然后遍历依次创建对应的log实现类。
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
if (result == null) {
throw new LogConfigurationException("");
}
return result;
}

//jcl内部默认的4个log实现类。
private static final String[] classesToDiscover = {
"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
};

//根据class创建log对象
private Log createLogFromClass(String logAdapterClassName,
String logCategory,
boolean affectState)
throws LogConfigurationException {

Object[] params = { logCategory };
Log logAdapter = null;
Constructor constructor = null;

Class logAdapterClass = null;
ClassLoader currentCL = getBaseClassLoader();

for(;;) {
logDiagnostic("Trying to load '" + logAdapterClassName + "' from classloader " + objectId(currentCL));
try {
Class c;
try {
//通过class.forName加载log类
c = Class.forName(logAdapterClassName, true, currentCL);
} catch (ClassNotFoundException originalClassNotFoundException) {
//表示找不到类(因为用户可能指定类log的实现类)
try {
c = Class.forName(logAdapterClassName);
} catch (ClassNotFoundException secondaryClassNotFoundException) {
break;
}
}
//拿到c的构造方法对象。然后通过构造对象来创建对象。(有参构造方法)
constructor = c.getConstructor(logConstructorSignature);
Object o = constructor.newInstance(params);

if (o instanceof Log) {
logAdapterClass = c;
logAdapter = (Log) o;
break;
}
handleFlawedHierarchy(currentCL, c);
} catch (NoClassDefFoundError e) {

String msg = e.getMessage();
logDiagnostic("");
break;
} catch (ExceptionInInitializerError e) {
String msg = e.getMessage();
logDiagnostic("");
break;
} catch (LogConfigurationException e) {
throw e;
} catch (Throwable t) {
handleThrowable(t);
handleFlawedDiscovery(logAdapterClassName, currentCL, t);
}
if (currentCL == null) {
break;
}
currentCL = getParentClassLoader(currentCL);
}
//返回创建的对象
return logAdapter;
}

通过分析jcl的代码可以得到:
jcl本身不实现日志记录,但是提供了记录日志的抽象方法即接口(info,debug,error…….)
底层通过一个数组存放具体的日志框架的类名,然后循环数组依次去匹配这些类名是否在项目中被依赖了,如果找到被依赖的则直接使用,所以他有先后顺序。
下图为jcl中存放日志技术类名的数组,默认有四个,后面两个可以忽略。
在这里插入图片描述
上面的代码81行就是通过一个类名去load一个class,如果load成功则直接new出来并且返回使用。如果没有load到class这循环第二个,直到找到为止。
可以看到这里的循环条件必须满足result不为空,也就是如果没有找到具体的日志依赖则继续循环,如果找到则条件不成立,不进行循环了。
总结:顺序log4j>jul

  • 虽然Log4JLogger是jcl的jar包中的类,但是该Log4JLogger类,依赖了log4j的类,当你没有引入log4j的依赖的时候,在创建Log4JLogger类,就会失败。
  • 下图是在没有引入log4j依赖的情况下,Log4JLogger类的情况图:
  • 在这里插入图片描述
  • 因为jul是java自带的日志类,所以在java环境下,jcl就算在创建log4j失败的情况下,也会去创建Jdk14Logger。可以看到Jdk14Logger是依赖了jul的。下图是Jdk14Logger的类图:
  • 在这里插入图片描述

jcl特点:
他不直接记录日志,他是通过第三方记录日志(jul)。
jcl是一个接口,默认有4个log实现类。

4.slf4j

slf4j他也不记录日志,通过绑定器绑定一个具体的日志记录来完成日志记录
官网:www.slf4j.org/

1
2
3
4
5
6
xml复制代码//slf4j依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
  • 在只添加了slf4j依赖,而没有添加任意一个绑定器,日志是不会打印的。控制台会输出warn信息,如下图:
  • 在这里插入图片描述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
xml复制代码//slf4j依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>

<!--slf4jbind绑定器,将slf4j绑定到jul-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>

<!--slf4jbind绑定器,将slf4j绑定到log4j-->
<!--<dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-log4j12</artif
<!-- <version>1.7.5</version>-->
<!--</dependency>-->

<!--slf4jbind绑定器,将slf4j绑定到jcl-->
<!--<dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-jcl</artifactI
<!-- <version>1.7.25</version>-->
<!--</dependency>-->
  • slf4j提供了很多的绑定器,有log4j,jul,jcl。
  • 当引入了jul绑定器之后,slf4j就能打印日志了,如下图:
  • 在这里插入图片描述

总结:

  1. slf4j需要打印日志,就一定需要引入绑定器。slf4j提供了很多的绑定器,有jul,jcl,log4j等。
  2. slf4j如果引入了jcl绑定器,因为jcl也是一个接口,jcl会加载log4j,jul。
  1. 如果你想使用log4j,也需要引入log4j的依赖,log4j的配置文件
  2. 如果你不引入log4j的依赖,就默认使用jul
  1. slf4j如果引入了log4j绑定器,需要log4j的配置文件(这个时候不用引入log4j的依赖了,因为该绑定器已经帮我们引入了)
  • 问题:有这样的一个业务系统:
  • 项目A,是使用了slf4j打印日志,然后通过slf4j绑定器,绑定到jul,然后使用jul打印日志。
  • 项目A使用了spring框架,但是spring框架使用jcl打印日志,spring引入了log4j依赖,使得spring框架是用log4j打印日志。
  • 这样的话,项目A就出现了多个日志框架打印日志了,那就很混乱,现在要求项目A只能使用一种日志框架技术,这个要怎么处理?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
xml复制代码//项目A的依赖
//log4j
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

//jcl
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

//slf4j
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>

<!--slf4jbind绑定器,将slf4j绑定到jul-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>
  • 就引入上面的依赖jar包,项目就会有两种日志输出,如下图所示:
  • 在这里插入图片描述

解决方案:

  1. 可以通过修改slf4j的绑定器,直接改用slf4j的log4j绑定器。

因为使用slf4j绑定到log4j,只需要简单的引入一个依赖即可。

  1. 使用slf4j的桥接器,将spring使用jcl打印日志这步切断,将jclj桥接到slf4j,然后再走项目A的日志打印。

因为使用jcl桥接到slf4j,只需要简单的引入一个依赖即可。

1
2
3
4
5
6
7
xml复制代码//增加下面这个依赖即可:
<!--slf4j桥接器,将jcl桥接到slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
  • 当使用了jcl-over-slf4j桥接器之后,可以使得jcl桥接到slf4j,然后再走slf4j这边的日志输出。从而实现将spring框架的日志输出,改为了slf4j–>jul
  • 在这里插入图片描述
  • 改进后的日志打印流程图如下:
  • 在这里插入图片描述

各种日志技术的关系和作用

  • 在这里插入图片描述

spring5新特性日志体系

Spring4当中依赖jcl,即Spring4当中采用的日志技术是jcl:commons-logging,即默认使用jul;加入log4j依赖和配置,即可切换为log4j
Spring5当中也是使用了jcl:spring-jcl,是重写为了jul框架。spring5使用的spring的jcl(spring改了jcl的代码)来记录日志的,但是jcl不能直接记录日志,采用循环优先的原则。

spring-jcl源码解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
java复制代码#AbstractApplicationContext
protected final Log logger = LogFactory.getLog(getClass());

#LogFactory(spring-jcl包下)
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}

public static Log getLog(String name) {
switch (logApi) {
//log4j2
case LOG4J:
return Log4jDelegate.createLog(name);
//slf4j
case SLF4J_LAL:
return Slf4jDelegate.createLocationAwareLog(name);
case SLF4J:
return Slf4jDelegate.createLog(name);
default:
//默认是jul
return JavaUtilDelegate.createLog(name);
}
}

//默认是jul
private static LogApi logApi = LogApi.JUL;
//静态代码块,在类初始化的时候执行
static {
ClassLoader cl = LogFactory.class.getClassLoader();
try {
// Try Log4j 2.x API(尝试加载log4j2)
cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger");
logApi = LogApi.LOG4J;
}
catch (ClassNotFoundException ex1) {
try {
// Try SLF4J 1.7 SPI(尝试加载slf4j)
cl.loadClass("org.slf4j.spi.LocationAwareLogger");
logApi = LogApi.SLF4J_LAL;
}
catch (ClassNotFoundException ex2) {
try {
// Try SLF4J 1.7 API(尝试加载slf4j)
cl.loadClass("org.slf4j.Logger");
logApi = LogApi.SLF4J;
}
catch (ClassNotFoundException ex3) {
// Keep java.util.logging as default(如果都没有,就保持使用默认的jul)
}
}
}
}

从上面spring5的源码可以看到,spring5使用的日志是spring-jcl,默认是jul,然后会依次加载log4j2,slf4j。在都加载不到的情况下,就使用默认的jul日志技术了。
因为spring5使用的是log4j2,所以在加入了log4j的依赖和配置文件,是不生效的。

  • spring5使用log4j2日志技术,需要加入的依赖和配置文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
xml复制代码<!-- spring5的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.9.RELEASE</version>
</dependency>

<!-- log4j2依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>

<!-- log4j2.xml配置文件 -->
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
  • 要将spring5改用log4j来记录日志,怎么实现?
  • 1.引入slf4j依赖,然后slf4j绑定log4j,并添加log4j的配置文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
xml复制代码<!-- spring5的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.9.RELEASE</version>
</dependency>

<!--slf4j依赖(可省略)-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>

<!--slf4jbind绑定器,将slf4j绑定到log4j(已经包含了slf4j和log4j的依赖)-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>

扩展:

Mybatis日志体系

  • mybatis的日志技术实现
  • 在这里插入图片描述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
typescript复制代码#LogFactory

private static void tryImplementation(Runnable runnable) {
//关键代码 logConstructor == null 没有找到实现则继续找
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}

tryImplementation(new Runnable() {
@Override
public void run() {
useSlf4jLogging();
}
});

public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}

private static void setImplementation(Class<? extends Log> implClass) {
try {
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}

具体实现类
mybatis提供很多日志的实现类,用来记录日志,取决于初始化的时候load到的class

  • jcl实现
  • 在这里插入图片描述
  • log4j2实现
  • 在这里插入图片描述
  • jul实现
  • 在这里插入图片描述

mybaits缓存问题:
1.mybaits在整合spring框架的时候,一级缓存会失效,原因是mybatis一级缓存是基于sqlSession的,整合了spring之后,spring会管理sqlSession,在查询完之后,会帮我们关闭sqlSession,所以导致缓存失效了。

2.mybatis的二级缓存。开启也是很简单在对应的mapper接口中加上@CacheNamespace注解即可。
备注:mybatis的二级缓存会有一个很大的坑。
因为mybatis的二级缓存是基于命名空间来实现了。当在不同的mapper接口操作了同一张表,这个就会有问题了,A接口更新了数据,B接口两次获取的数据都会是一样的。

mybatis打印sql日志分析

mybaits加载日志技术的顺序是:slf4j–>jcl—>log4j2—>log4j–>jul—>nolog
1.在spring4+mybatis+log4j的情况会有sql日志输出。

因为spring4使用的是jcl,jcl在引入log4j依赖下,就会使用了log4j技术打印sql日志。

2.在spring5+mybatis+log4j的情况不会有sql日志输出。

因为spring5使用的是spring-jcl(本质也是jcl),spring-jcl默认使用了jul(不再使用log4j而是用log4j2,在上面有详细说明)。
由于mybatis加载日志的顺序,jcl是先与log4j,所以该情况下导致了mybatis使用了jcl技术。

那么问题来了?为什么mybatis在使用jul的情况下,没有打印sql日志?

当使用了jul的时候,jul默认的日志级别是 INFO
原生的jdk的logging的日志级别是FINEST、FINE、INFO、WARNING、SEVERE分别对应我们常见的trace、debug、info、warn、error

  • 通过下图可以看到jdk14的isDebugEnabled()方法,是使用了Level.FINE,这个值是500
  • 在这里插入图片描述
  • 跟踪isLoggable()方法,可以看到下图,level.intValue()就是上面传递进去的Level.FINE是500,levelValue是800
  • 在这里插入图片描述
  • 这里跟踪一下levelValue的值,可以看到levelValue = Level.INFO.intValue() , INFO的值是800
  • 在这里插入图片描述
  • 在这里插入图片描述
  • 跟踪mybatis打印sql日志的源码,是判断了isDebugEnabled()返回的值,所以mybatis在使用jul的时候,是不能打印sql日志的。
  • 在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%