java自带的一个日志记录的技术,直接使用
java.util.logging.Logger
//log4j依赖
log4j
log4j
1.2.17
//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特点:
可以不需要依赖第三方的技术,直接记录日志。
//jcl依赖
commons-logging
commons-logging
1.2
//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)信息。
#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
通过分析jcl的代码可以得到:
jcl本身不实现日志记录,但是提供了记录日志的抽象方法即接口(info,debug,error…)
底层通过一个数组存放具体的日志框架的类名,然后循环数组依次去匹配这些类名是否在项目中被依赖了,如果找到被依赖的则直接使用,所以他有先后顺序。
下图为jcl中存放日志技术类名的数组,默认有四个,后面两个可以忽略。
上面的代码81行就是通过一个类名去load一个class,如果load成功则直接new出来并且返回使用。如果没有load到class这循环第二个,直到找到为止。
可以看到这里的循环条件必须满足result不为空,也就是如果没有找到具体的日志依赖则继续循环,如果找到则条件不成立,不进行循环了。
总结:顺序log4j>jul
jcl特点:
他不直接记录日志,他是通过第三方记录日志(jul)。
jcl是一个接口,默认有4个log实现类。
slf4j他也不记录日志,通过绑定器绑定一个具体的日志记录来完成日志记录
官网:https://www.slf4j.org/
//slf4j依赖
org.slf4j
slf4j-api
1.7.25
//slf4j依赖
org.slf4j
slf4j-api
1.7.25
org.slf4j
slf4j-jdk14
1.7.25
总结:
- slf4j需要打印日志,就一定需要引入绑定器。slf4j提供了很多的绑定器,有jul,jcl,log4j等。
- slf4j如果引入了jcl绑定器,因为jcl也是一个接口,jcl会加载log4j,jul。
- 如果你想使用log4j,也需要引入log4j的依赖,log4j的配置文件
- 如果你不引入log4j的依赖,就默认使用jul
- slf4j如果引入了log4j绑定器,需要log4j的配置文件(这个时候不用引入log4j的依赖了,因为该绑定器已经帮我们引入了)
//项目A的依赖
//log4j
log4j
log4j
1.2.17
//jcl
commons-logging
commons-logging
1.2
//slf4j
org.slf4j
slf4j-api
1.7.25
org.slf4j
slf4j-jdk14
1.7.25
解决方案:
- 可以通过修改slf4j的绑定器,直接改用slf4j的log4j绑定器。
因为使用slf4j绑定到log4j,只需要简单的引入一个依赖即可。
- 使用slf4j的桥接器,将spring使用jcl打印日志这步切断,将jclj桥接到slf4j,然后再走项目A的日志打印。
因为使用jcl桥接到slf4j,只需要简单的引入一个依赖即可。
//增加下面这个依赖即可:
org.slf4j
jcl-over-slf4j
1.7.25
Spring4当中依赖jcl,即Spring4当中采用的日志技术是jcl:commons-logging,即默认使用jul;加入log4j依赖和配置,即可切换为log4j
Spring5当中也是使用了jcl:spring-jcl,是重写为了jul框架。spring5使用的spring的jcl(spring改了jcl的代码)来记录日志的,但是jcl不能直接记录日志,采用循环优先的原则。
#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的依赖和配置文件,是不生效的。
org.springframework
spring-context
5.0.9.RELEASE
org.apache.logging.log4j
log4j-core
2.8.2
org.springframework
spring-context
5.0.9.RELEASE
org.slf4j
slf4j-api
1.7.25
org.slf4j
slf4j-log4j12
1.7.5
#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
mybaits缓存问题:
1.mybaits在整合spring框架的时候,一级缓存会失效,原因是mybatis一级缓存是基于sqlSession的,整合了spring之后,spring会管理sqlSession,在查询完之后,会帮我们关闭sqlSession,所以导致缓存失效了。2.mybatis的二级缓存。开启也是很简单在对应的mapper接口中加上@CacheNamespace注解即可。
备注:mybatis的二级缓存会有一个很大的坑。
因为mybatis的二级缓存是基于命名空间来实现了。当在不同的mapper接口操作了同一张表,这个就会有问题了,A接口更新了数据,B接口两次获取的数据都会是一样的。
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