slf4j日志框架源码阅读
slf4j日志框架源码阅读
private static final Logger log = LoggerFactory.getLogger(App.class);
- Logger logger = getLogger(clazz.getName());
1.1 ILoggerFactory iLoggerFactory = getILoggerFactory(); 参见 getILoggerFactory方法详细解析 章节
//获取 日志实现框架的 LoggerFactory;这个必须要实现 slf4j 的 org.slf4j.ILoggerFactory 接口
1.2 return iLoggerFactory.getLogger(name); //获取到 日志实现框架的 logger - DETECT_LOGGER_NAME_MISMATCH
- return logger;
getILoggerFactory 方法详细解析
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) { //首次 getILoggerFactory 的话,那么 INITIALIZATION_STATE 一定是 UNINITIALIZED
synchronized (LoggerFactory.class) { //这里有 类锁,
if (INITIALIZATION_STATE == UNINITIALIZED) { //再次 检查 INITIALIZATION_STATE 状态, 双重检查锁 ( INITIALIZATION_STATE 是 volatile 类型得的)
INITIALIZATION_STATE = ONGOING_INITIALIZATION; //设置 INITIALIZATION_STATE 为 ONGOING_INITIALIZATION 状态
performInitialization(); //继续 performInitialization 方法
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION: //首次绑定成功 或者 第二次 使用 getILoggerFactory 方法,最会 走到这一步
return StaticLoggerBinder.getSingleton().getLoggerFactory(); //获取 日志实现框架的 LoggerFactory;这个必须要实现 slf4j 的 org.slf4j.ILoggerFactory 接口
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
performInitialization 方法详细解析
// 初始化 方法
private final static void performInitialization() {
bind(); //绑定具体的 log 实现框架;继续 bind 方法
//如果 bind 成功的话,INITIALIZATION_STATE 则已经是 SUCCESSFUL_INITIALIZATION 了
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck(); //版本相关检查
}
}
bind 方法详细解析
private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
if (!isAndroid()) { //非 android 环境
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); //找到classpath 中 所有的 org/slf4j/impl/StaticLoggerBinder.class 路径
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); // 如果 找到了 多个 org/slf4j/impl/StaticLoggerBinder.class 那么则会 打印 提示 每个 org/slf4j/impl/StaticLoggerBinder.class 的 路径
}
// the next line does the binding
StaticLoggerBinder.getSingleton(); //注意这个 StaticLoggerBinder 就是 上面找到的 org/slf4j/impl/StaticLoggerBinder.class ,这个需要每个不同的 日志框架自己去实现
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; // INITIALIZATION_STATE 设为 SUCCESSFUL_INITIALIZATION 状态
reportActualBinding(staticLoggerBinderPathSet); //如果 找到了 多个 org/slf4j/impl/StaticLoggerBinder.class 那么则会 打印 提示 实际使用的是 那个 框架的 log
} catch (NoClassDefFoundError ncde) { //这里是没有找到 日志实现框架的情况
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
} finally {
postBindCleanUp();
}
}
总结
通过上面我们对 slf4j 的源码阅读,我们明白了,slf4j 是如何 找到 具体日志实现 的过程。
也明白了 具体日志实现 需要实现 slf4j 的 一些 特定类 和实现某些接口的。
org/slf4j/impl/StaticLoggerBinder.class 桥接类
org.slf4j.ILoggerFactory logger 接口
所以我们在使用 具体的 日志实现框架的时候,是需要 引入 其和 slf4j 之间的 桥接包的。
使用 slf4j 需要 引入的 jar 包,也是使用日志 必须引入的 jar 包
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
使用 logback 需要 引入的 jar 包
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
logback-classic 包 依赖 logback-core包,logback-classic 里面就是有 org/slf4j/impl/StaticLoggerBinder.class 桥接类
和 org.slf4j.ILoggerFactory logger 接口 的实现类
日志配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="USER_HOME" value="./log" />
<appender name="FILE-THREAD" class="ch.qos.logback.classic.sift.ParrelSiftingAppender">
<!-- This is MDC value -->
<!-- We will assign a value to 'logFileName' via Java code -->
<discriminator>
<key>logFileName</key>
<defaultValue>head0</defaultValue>
</discriminator>
<sift>
<!-- A standard RollingFileAppender, the log file is based on 'logFileName' at runtime -->
<appender name="FILE-${logFileName}"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${USER_HOME}/${logFileName}.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>
%d{yyyy-MM-dd HH:mm:ss} %mdc [%thread] %level %logger{35} - %msg%n
</Pattern>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<FileNamePattern>${USER_HOME}/${logFileName}.%i.log.zip
</FileNamePattern>
<MinIndex>1</MinIndex>
<MaxIndex>10</MaxIndex>
</rollingPolicy>
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
</sift>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%-5level %logger{36} - %msg%n
</Pattern>
</layout>
</appender>
<logger name="org.yyb" level="debug"
additivity="false">
<appender-ref ref="FILE-THREAD" />
<appender-ref ref="STDOUT" />
</logger>
<root level="error">
<appender-ref ref="STDOUT" />
</root>
</configuration>
使用 log4j 需要 引入的 jar 包
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>v
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
slf4j-log4j12 是桥接包,log4j 是 真正的log实现
日志配置
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
log4j.rootLogger=INFO, stdout, connectAppender
#log4j.rootLogger=INFO, stdout
# Send the logs to the console.
#
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Send the logs to a file, rolling the file at midnight local time. For example, the `File` option specifies the
# location of the log files (e.g. ${kafka.logs.dir}/connect.log), and at midnight local time the file is closed
# and copied in the same directory but with a filename that ends in the `DatePattern` option.
#
log4j.appender.connectAppender=com.smcv.org.apache.log4j.ThreadWriterAppender
log4j.appender.connectAppender.append=org.apache.log4j.DailyRollingFileAppender
log4j.appender.connectAppender.DatePattern='.'yyyy-MM-dd-HH
log4j.appender.connectAppender.fileName=log/$X{threadName}connect.log
log4j.appender.connectAppender.layout=org.apache.log4j.PatternLayout
# The `%X{connector.context}` parameter in the layout includes connector-specific and task-specific information
# in the log message, where appropriate. This makes it easier to identify those log messages that apply to a
# specific connector. Simply add this parameter to the log layout configuration below to include the contextual information.
#
connect.log.pattern=[%d] %p %m (%c:%L)%n
#connect.log.pattern=[%d] %p %X{connector.context}%m (%c:%L)%n
log4j.appender.stdout.layout.ConversionPattern=${connect.log.pattern}
log4j.appender.connectAppender.layout.ConversionPattern=${connect.log.pattern}
log4j.logger.org.apache.zookeeper=ERROR
log4j.logger.org.reflections=ERROR
使用 log4j2 需要 引入的 jar 包
<!--添加log4j2相关jar包-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
log4j-slf4j-impl 是 桥接包