文章目录
- 概要
- 常用日志框架
- 常见框架有以下:
- slf4j StaticLoggerBinder绑定过程(slf4j-api-1.7.32 )
- JCL 运行时动态查找过程:(commons-logging-1.2)
- 使用桥接修改具体日志实现
- 一行日志的打印过程
- 开源框架日志模块
- Spring && Springboot
- Mybatis
概要
文章简要梳理java常见日志框架,学习其背景和实现,通过阅读文章可以掌握如下知识点:
1、学习目前常用日志接口和实现框架,了解其功能作用实现原理。
2、掌握slf4j和logback的日志组合框架,debug跟踪一行日志的打印流程。
3、学习日常开发框架如Spring, Mybatis,了解大佬是如何实现日志模块。
常用日志框架
一、日志框架主要分为两类,日志门面接口和日志具体实现。
- 日志门面接口,如JCL 和 slf4j
1)应用面向接口编程,接口不易变动。
2)面向接口,可以通过绑定或桥接切换不同日志系统实现。 - 日志具体实现,如log4j JUL logback log4j2等。
1)专注于实现日志打印功能实现。
2)提供各种日志配置和功能特性。
常见框架有以下:
框架 | 功能 | 描述 |
---|---|---|
log4j | 实现 | Apache 早期开源日志框架 |
JUL java.util.logging | 实现 | Sun官方自带日志框架,JDK1.4引入 |
JCL Apache Commons Logging | 接口 | Apache 的日志门面,可以切换log4j或JUL具体日志实现 |
slf4j-api | 接口 | 简单java日志接口,日志门面,简单易用 |
logback | 实现 | 高性能日志实现框架,包含一下模块: logback-core:基础模块 logback-classic:日志实现模块 |
log4j2 | 实现 | Apache 的开源日志框架,性能优化版本 |
slf4j-jcl slf4j-jdk14 slf4j-log412 | 绑定 | slf4j-api具体绑定实现 |
jcl-over-slf4j jul-to-slf4j log4j-over-slf4j | 桥接 | 其他日志框架slf4j-api具体绑定实现 |
日志分层可参考如下图:
绑定查找具体实现过程:
slf4j StaticLoggerBinder绑定过程(slf4j-api-1.7.32 )
- 如果未初始化,执行初始化:LoggerFactory#performInitialization。
- 扫描类文件:org/slf4j/impl/StaticLoggerBinder.class,报告不存在类或存在多个类歧义。
- 由具体实现框架提logback-classic提供StaticLoggerBinder,触发其静态绑定。
TIP:sl4j-api 2.x版本使用SPI org.slf4j.spi.SLF4JServiceProvider,
logback提供service实现 ch.qos.logback.classic.spi.LogbackServiceProvider
JCL 运行时动态查找过程:(commons-logging-1.2)
- System.getProperty读org.apache.commons.logging.LogFactory
- 读META-INF/services/org.apache.commons.logging.LogFactory
- 读取类路径下commons-logging.properties,key=org.apache.commons.logging.LogFactory
- 使用默认实现org.apache.commons.logging.impl.LogFactoryImpl,按下面顺序获取Logger:
Log4JLogger
Jdk14Logger
Jdk13LumberjackLogger
SimpleLog
使用桥接修改具体日志实现
如果项目依赖第三包已经其他日志框架接口,那么这时候如何规范化统一日志实现,这时候可以使用桥接:
一行日志的打印过程
下面介绍一行日志的打印过程,以目前市面常见搭配组合slf4j-api-1.7.32 logback-classic-1.2.12为例,代码只展示部分关键源码:
1、LoggerFactory#getLogger(Class<?> clazz),
获取指定名称Logger。
public static Logger getLogger(String name) {ILoggerFactory iLoggerFactory = getILoggerFactory();return iLoggerFactory.getLogger(name);
}
2、LoggerFactory#getILoggerFactory,
获取日志工厂LoggerFactory,如果未初始化则执行performInitialization完成日志实现绑定。
public static ILoggerFactory getILoggerFactory() {if (INITIALIZATION_STATE == UNINITIALIZED) {synchronized (LoggerFactory.class) {if (INITIALIZATION_STATE == UNINITIALIZED) {INITIALIZATION_STATE = ONGOING_INITIALIZATION;performInitialization();}}}……}
3、LoggerFactory#bind,
绑定到具体日志实现,扫描类路径资源文件org/slf4j/impl/StaticLoggerBinder.class,加载完成绑定。
private final static void bind() {staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);……
}
4、StaticLoggerBinder.getSingleton().getLoggerFactory(),
具体日志实现绑定初始化,如绑定到logback的StaticLoggerBinder,调用logback实现类StaticLoggerBinder#init->ContextInitializer#autoConfig,实现具体绑定类初始化。
logback ContextInitializer配置
1)ContextInitializer#configureByResource,按顺序检查如下配置文件logback-test.xml,logback.groovy,logback.xml,则存在使用配置文件。
2)SPI 加载是否指定Configurator配置实现,是则使用该配置实现。
3)如果上述都没有,则使用默认配置实现BasicConfigurator。
public ILoggerFactory getLoggerFactory() {public void autoConfig() throws JoranException {StatusListenerConfigHelper.installIfAsked(loggerContext);URL url = findURLOfDefaultConfigurationFile(true);if (url != null) {configureByResource(url);} else {Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);if (c != null) {try {c.setContext(loggerContext);c.configure(loggerContext);} catch (Exception e) {throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass().getCanonicalName() : "null"), e);}} else {BasicConfigurator basicConfigurator = new BasicConfigurator();basicConfigurator.setContext(loggerContext);basicConfigurator.configure(loggerContext);}}}
}
5、LoggerContext#getLogger,
获取指定名称logger。
public final Logger getLogger(final String name) {……synchronized (logger) {childLogger = logger.getChildByName(childName);if (childLogger == null) {childLogger = logger.createChildByName(childName);loggerCache.put(childName, childLogger);incSize();}}……
}
6、Logger#info,
具体一行日志打印,以Info级别日志打印为例:
- 全局过滤器 TurboFilter判断是否打印
2)构建LoggingEvent,将日志事件投递给Appender#doAppend
3)调用Appender配置过滤Filter判断是否打印
4)交给具体Appender完成日志事件处理,实现有:
控制台ConsoleAppender
文件FileAppender
归档文件RollingFileAppender
异步AsyncAppender
数据库DBAppender
等等。
private void filterAndLog_0_Or3Plus(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,final Throwable t) {final FilterReply decision = loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);if (decision == FilterReply.NEUTRAL) {if (effectiveLevelInt > level.levelInt) {return;}} else if (decision == FilterReply.DENY) {return;}buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
}
其他日志打印格式化相关:
Appender
Encoder
Layout
开源框架日志模块
Spring && Springboot
Spring使用JCL门面,如果不引入commons-logging或其他实现jar。按上面描述会使用JDK自带JUL作为日志实现。
Springboot提供logging的starter。
spring-boot-starter-logging, 默认使用logback实现,桥接了log4j和JUL到slf4j。
TIP: 如果项目有引入commons-logging, 还需要手动引入桥接 jcl-over-slf4j
<dependencies><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version><scope>compile</scope></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-to-slf4j</artifactId><version>2.13.3</version><scope>compile</scope></dependency><dependency><groupId>org.slf4j</groupId><artifactId>jul-to-slf4j</artifactId><version>1.7.30</version><scope>compile</scope></dependency></dependencies>
Springboot框架日志初始化流程如下:
1、监听Spring容器生命周期
2)LoggingSystem#get,加载具体日志系统实现,默认按如下优先级:
3)日志系统切入Springboot应用生命周期
监听ApplicationStartingEvent,触发LoggingSystem#beforeInitialize
监听ApplicationEnvironmentPreparedEvent,触发LoggingSystem#initialize
监听onApplicationPreparedEvent,注册日志系统相关单例bean到Spring容器
监听ContextClosedEvent/onApplicationFailedEvent, 日志系统清理
Mybatis
Mybatis对常见日志框架包装一层,使用自定义日志接口,通过配置或默认规则设置具体日志实现框架:
1)尝试加载实现,按如下顺序优先级加载。
2)如果通过配置指定具体实现,则使用具体实现。
Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);