likes
comments
collection
share

spring5新特性日志体系

作者站长头像
站长
· 阅读数 12

spring5新特性日志体系

主流的log技术名词
1.jul

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

2.log4j
//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
//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源码分析:
#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中存放日志技术类名的数组,默认有四个,后面两个可以忽略。 spring5新特性日志体系 上面的代码81行就是通过一个类名去load一个class,如果load成功则直接new出来并且返回使用。如果没有load到class这循环第二个,直到找到为止。 可以看到这里的循环条件必须满足result不为空,也就是如果没有找到具体的日志依赖则继续循环,如果找到则条件不成立,不进行循环了。 总结:顺序log4j>jul

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

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

4.slf4j

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

//slf4j依赖
<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.7.25</version>
</dependency>
  • 在只添加了slf4j依赖,而没有添加任意一个绑定器,日志是不会打印的。控制台会输出warn信息,如下图:
  • spring5新特性日志体系
//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就能打印日志了,如下图:
  • spring5新特性日志体系

总结:

  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只能使用一种日志框架技术,这个要怎么处理?
//项目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包,项目就会有两种日志输出,如下图所示:
  • spring5新特性日志体系

解决方案:

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

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

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

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

//增加下面这个依赖即可:
<!--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新特性日志体系
  • 改进后的日志打印流程图如下:
  • spring5新特性日志体系

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

  • spring5新特性日志体系

spring5新特性日志体系

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

spring-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的依赖和配置文件,是不生效的。

  • spring5使用log4j2日志技术,需要加入的依赖和配置文件。
<!-- 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的配置文件。
<!-- 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的日志技术实现
  • spring5新特性日志体系
#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实现
  • spring5新特性日志体系
  • log4j2实现
  • spring5新特性日志体系
  • jul实现
  • spring5新特性日志体系

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
  • spring5新特性日志体系
  • 跟踪isLoggable()方法,可以看到下图,level.intValue()就是上面传递进去的Level.FINE是500,levelValue是800
  • spring5新特性日志体系
  • 这里跟踪一下levelValue的值,可以看到levelValue = Level.INFO.intValue() , INFO的值是800
  • spring5新特性日志体系
  • spring5新特性日志体系
  • 跟踪mybatis打印sql日志的源码,是判断了isDebugEnabled()返回的值,所以mybatis在使用jul的时候,是不能打印sql日志的。
  • spring5新特性日志体系
转载自:https://juejin.cn/post/6991665416883929119
评论
请登录