当前位置:网站首页>logback源码阅读(一)获取ILoggerFactory、Logger
logback源码阅读(一)获取ILoggerFactory、Logger
2022-08-02 14:00:00 【Ethan_199402】
什么是slf4j
说到logback,必会提及slf4j。slf4j的全称是 : The Simple Logging Facade for Java,是java的一个日志门面,堪称Facade设计模式的最佳代言人! slf4j制定了一套标准接口,从而实现在[日志]这个领域里一统江湖。主要做两件事:
- 实现了Logger获取的通用api: LoggreFactory.getLogger(), 该方法基本上确定了获取L ogger的基础套路,约定了实现sIf4j的日志框架需要实现org.slf4j.impl.StaticLoggerBinder,完成与slf4j的集成。
- 定义了ILoggerFactory, Logger等系列接口,该接口由具体日志框架实现。
值得强调的是:flf4j是接口,提供API,而 java.util.logging,logback,log4j等才是真正的日志框架
logback
Logback是由log4j创始人设计的另一个开源日志组件,官方网站: http://logback.qos.ch 是log4j的改良版本,具有更快的实现:
Logback的内核重写了,在一些关键执行路径上性能提升10倍以上。而且logback不仅性能提升了,初始化内存加载也更小了。
logback 初始化
当一个类使用了@Slf4j注解,lombok是会自动为该类生成一个logger字段的,下面是Slf4j注解的注释
Example:
@Slf4j
public class LogExample {
}
will generate:
public class LogExample {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
}
我们随便打开一个使用了Slf4j注解的类,查看Structure,可以看到的确生成了对应的logger
那么,我们就从LoggerFactory.getLogger(this.getClass())
这个方法入手,开始一探究竟,本文源码为slf4f-api-1.7.36
getLogger()
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
可以看到,调用getLogger获取logger,如果 clazz 参数与 SLF4J 内部计算的调用者名称不同,则会打印记录器名称不匹配警告,但前提是 slf4j.detectLoggerNameMismatch 系统属性设置为 true。默认情况下,此属性未设置,即使在记录器名称不匹配的情况下也不会打印任何警告。
继续查看getLogger方法
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
每种日志框架都实现了LoggerFactory 这里就是根据集成的日志框架获得对应的factory,接下来看getILoggerFactory方法如何实现
public static ILoggerFactory getILoggerFactory() {
//没有出初始化则加锁初始化并标记为正在初始化状态
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
//执行初始化
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION://初始化成功,返回loggerFactory
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION://没有找到StaticLoggerBinder则返回NOPLoggerFactory
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
//如果 正在初始化,返回替代工厂SubstituteLoggerFactory,日志一般也是委托给NOPLogger
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
static final int UNINITIALIZED = 0;//未初始化
static final int ONGOING_INITIALIZATION = 1;//正在初始化
static final int FAILED_INITIALIZATION = 2;//初始化失败,没有实现LoggerFactoryBinder 相关方法,一般是版本过低
static final int SUCCESSFUL_INITIALIZATION = 3;//初始化成功
static final int NOP_FALLBACK_INITIALIZATION = 4;//找不到StaticLoggerBinder
我们最关心的应该是performInitialization();
private final static void performInitialization() {
bind();
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()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
//打印出实际的日志绑定器 ContextSelectorStaticBinder
reportActualBinding(staticLoggerBinderPathSet);
} 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();
}
}
- 非android平台时找到可能的静态日志绑定器的路径集合findPossibleStaticLoggerBinderPathSet()(在classpath查找 org/slf4j/impl/StaticLoggerBinder.class)
- 如果日志有多个绑定器,打印到控制台reportMultipleBindingAmbiguity
如果是android平台,忽略
依次打印出多个日志绑定器,并给出文档提示 - 获得唯一的静态日志绑定器StaticLoggerBinder.getSingleton()
绑定器内部持有LoggerContext和ContextSelectorStaticBinder - 设置初始化状态为成功
- 打印出实际的日志绑定器 ContextSelectorStaticBinder
其中1.2源码比较简单不在展开,重点是第三步
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
静态代码块触发init方法
static {
SINGLETON.init();
}
void init() {
try {
try {
//委托ContextInitializer初始化LoggerContext
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// logback-292
//如果没有配置状态监听器,则打印出警告
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
//上下文选择绑定器初始化
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Exception t) {
// see LOGBACK-1159
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}
ContextInitializer.autoConfig()
public void autoConfig() throws JoranException {
//如果没有,安装状态监听器
StatusListenerConfigHelper.installIfAsked(loggerContext);
//找到默认的配置文件或者URL,一次按照系统属性logback.configurationFile查找,按照logback-test.xml,logback.groovy,logback.xml得到配置文件
URL url = findURLOfDefaultConfigurationFile(true);
if (url != null) {
//如果找到了,configureByResource(url)
configureByResource(url);
} else {
//否则,按照spi的方式找到Configurator的实现类,设置上下文,进行配置
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 {
//如果spi方式拿不到,则使用缺省的BasicConfigurator(里面只配置了一个控制台)设置上下文,进行配置
BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
}
}
}
- 找到默认的配置文件或者URL,依次按照系统属性logback.configurationFile查找,按照logback-test.xml,logback.xml得到配置文件。
在这里插入代码片
- 如果找到了,configureByResource(url)进行配置
- 按照spi的方式找到Configurator的实现类,设置上下文,进行配置
- 如果spi方式拿不到,则使用缺省的BasicConfigurator(里面只配置了一个控制台)设置上下文,进行配置
final public static String AUTOCONFIG_FILE = "logback.xml";
final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
final public static String CONFIG_FILE_PROPERTY = "logback.configurationFile";
至此,初始化成功,将初始化状态标记为初始化成功,打印日志
versionSanityCheck
得到StaticLoggerBinder的版本,并进行判断是否合适。
LoggerFactory放了允许使用的StaticLoggerBinder的版本,如果不合适,会答应出警告
private final static void versionSanityCheck() {
try {
String requested = StaticLoggerBinder.REQUESTED_API_VERSION;
boolean match = false;
for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
match = true;
}
}
if (!match) {
Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
+ Arrays.asList(API_COMPATIBILITY_LIST).toString());
Util.report("See " + VERSION_MISMATCH + " for further details.");
}
} catch (java.lang.NoSuchFieldError nsfe) {
// given our large user base and SLF4J's commitment to backward
// compatibility, we cannot cry here. Only for implementations
// which willingly declare a REQUESTED_API_VERSION field do we
// emit compatibility warnings.
} catch (Throwable e) {
// we should never reach here
Util.report("Unexpected problem occured during version sanity check", e);
}
}
getLoggerFactory()
performInitialization方法执行完毕,继续执行StaticLoggerBinder.getSingleton().getLoggerFactory()
public ILoggerFactory getLoggerFactory() {
if (!initialized) {
return defaultLoggerContext;
}
if (contextSelectorBinder.getContextSelector() == null) {
throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
}
return contextSelectorBinder.getContextSelector().getLoggerContext();
}
如果没有初始化过,则返回private LoggerContext defaultLoggerContext = new LoggerContext();
,初始化我们之前刚讲过如下
最后返回loggerContext,其继承了ILoggerFactory,至此得到ILoggerFactory,接下来是获取logger。
public final Logger getLogger(final String name) {
if (name == null) {
throw new IllegalArgumentException("name argument cannot be null");
}
// if we are asking for the root logger, then let us return it without
// wasting time
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
return root;
}
int i = 0;
Logger logger = root;
// check if the desired logger exists, if it does, return it
// without further ado.
Logger childLogger = (Logger) loggerCache.get(name);
// if we have the child, then let us return it without wasting time
if (childLogger != null) {
return childLogger;
}
// if the desired logger does not exist, them create all the loggers
// in between as well (if they don't already exist)
String childName;
while (true) {
int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
if (h == -1) {
childName = name;
} else {
childName = name.substring(0, h);
}
// move i left of the last point
i = h + 1;
synchronized (logger) {
childLogger = logger.getChildByName(childName);
if (childLogger == null) {
childLogger = logger.createChildByName(childName);
loggerCache.put(childName, childLogger);
incSize();
}
}
logger = childLogger;
if (h == -1) {
return childLogger;
}
}
}
- 如果name是root不分大小写,直接返回根logger
- 如果logger已经创建过,直接从缓存获取并返回
- 如果没有创建过,则创建logger,并维护父子关系,放入缓存
代码很简单,主要看一下createChildByName方法
Logger createChildByName(final String childName) {
int i_index = LoggerNameUtil.getSeparatorIndexOf(childName, this.name.length() + 1);
if (i_index != -1) {
throw new IllegalArgumentException("For logger [" + this.name + "] child name [" + childName
+ " passed as parameter, may not include '.' after index" + (this.name.length() + 1));
}
if (childrenList == null) {
childrenList = new CopyOnWriteArrayList<Logger>();
}
Logger childLogger;
childLogger = new Logger(childName, this, this.loggerContext);
childrenList.add(childLogger);
childLogger.effectiveLevelInt = this.effectiveLevelInt;
return childLogger;
}
写的也很明白,创建新的子logger并设置日志生效级别
举个例子:当我们把@slf4f放在一个com.ethan.test.demo的类上,那么上述代码会同时生成的logger为:
com,com.ethan,com.ethan.test,com.ethan.test.demo
边栏推荐
- MySQL - ERROR 1045 (28000): Access denied for user ‘root’@‘localhost’ (using password: YES)
- SQL函数 USER
- 保姆级教程:写出自己的移动应用和小程序(篇三)
- A number of embassies and consulates abroad have issued reminders about travel to China, personal and property safety
- 机器学习——交叉验证法
- 网络安全第三次作业
- 如何解决mysql服务无法启动1069
- 目前想通过提取本地excel文件创建数据表,在sql语句这出了一些问题
- eclipse连接数据库后插入数据报错null
- Object detection scene SSD-Mobilenetv1-FPN
猜你喜欢
二极管及其应用
How to connect DBeaver TDengine?
RHCE第一天作业
Supervision strikes again, what about the market outlook?2021-05-22
拯救流浪猫 | 「喵先锋」系列数字版权盲盒明日开抢
乐心湖‘s Blog——MySQL入门到精通 —— 囊括 MySQL 入门 以及 SQL 语句优化 —— 索引原理 —— 性能分析 —— 存储引擎特点以及选择 —— 面试题
打破文件锁限制,以存储力量助力企业增长新动力
【C语言】手撕循环结构 —— for语句
矩阵中的路径
k8s之KubeSphere部署有状态数据库中间件服务 mysql、redis、mongo
随机推荐
你真的懂单例模式么
Summer training camp-week2 graph theory
文件加密软件有哪些?保障你的文件安全
网络安全第一次作业
定了!就在7月30日!
rhce第三天作业
els strip collision deformation judgment
Mysql 基本操作指南之mysql查询语句
leetcode 504. Base 7 七进制数 (简单)
How to improve the originality of self-media creation and create popular works?
Image retrieval method based on deep learning!
大而全的pom文件示例
为什么四个字节的float表示的范围比八个字节的long要广
WiFi Association&Omnipeek抓包分析
智能指针-使用、避坑和实现
[C language] Analysis of function recursion (2)
不精确微分/不完全微分(Inexact differential/Imperfect differential)
web测试和app测试的区别?
Why does a four-byte float represent a wider range than an eight-byte long
Detailed explanation of ORACLE expdp/impdp