slf4j+logback日志框架源码解析
项目搭建
使用idea工具新建maven项目,引入依赖:
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.15.RELEASE</version>
</dependency>
</dependencies>
日志使用场景(源码入口)
private Logger logger = LoggerFactory.getLogger(UserController.class);
slf4j源码
(1)调用静态方法getLogger方法,根据类名称获取logger对象
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;
}
(2)要获取logger对象,首先需要获取ILoggerFactory对象
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
(3)调用getILoggerFactory方法获取ILoggerFactory对象,INITIALIZATION_STATE为初始化状态,有5中状态,0-未初始化;1-正在初始化;2-无获取单例对象的方法;3-初始化成功;4-无操作记录器实现。初次调用,初始化状态为0,在方法中会将初始化状态置为1,并进行初始化
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:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
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");
}
(4)初始化分为两个步骤:1.绑定StaticLoggerBinder;2.版本兼容校验
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
a.绑定,验证是否存在org/slf4j/impl/StaticLoggerBinder类,以及是否存在多个StaticLoggerBinder,并初始化StaticLoggerBinder的单例对象,更新初始化状态为成功
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;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_FACTORY.clear();
} 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);
}
}
b.版本兼容性校验,笔者使用的slf4j-api:1.7.25,其兼容的StaticLoggerBinder类的版本是以1.6或1.7开头的
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);
}
}
logback源码
(1)StaticLoggerBinder类初始化,获取配置文件中的参数自动配置
void init() {
try {
try {
(new ContextInitializer(this.defaultLoggerContext)).autoConfig();
} catch (JoranException var2) {
Util.report("Failed to auto configure default logger context", var2);
}
if (!StatusUtil.contextHasStatusListener(this.defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(this.defaultLoggerContext);
}
this.contextSelectorBinder.init(this.defaultLoggerContext, KEY);
this.initialized = true;
} catch (Exception var3) {
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", var3);
}
}
(2)自动配置,获取配置文件
public void autoConfig() throws JoranException {
StatusListenerConfigHelper.installIfAsked(this.loggerContext);
URL url = this.findURLOfDefaultConfigurationFile(true);
if (url != null) {
this.configureByResource(url);
} else {
Configurator c = (Configurator)EnvUtil.loadFromServiceLoader(Configurator.class);
if (c != null) {
try {
c.setContext(this.loggerContext);
c.configure(this.loggerContext);
} catch (Exception var4) {
throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass().getCanonicalName() : "null"), var4);
}
} else {
BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(this.loggerContext);
basicConfigurator.configure(this.loggerContext);
}
}
}
a.以logback-test.xmllogback.groovylogback.xml的顺序查找日志配置文件
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
URL url = this.findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
} else {
url = this.getResource("logback-test.xml", myClassLoader, updateStatus);
if (url != null) {
return url;
} else {
url = this.getResource("logback.groovy", myClassLoader, updateStatus);
return url != null ? url : this.getResource("logback.xml", myClassLoader, updateStatus);
}
}
}
b.获取配置文件url,例如:file:/E:/workSpace/ops/target/classes/logback.xml
private URL getResource(String filename, ClassLoader myClassLoader, boolean updateStatus) {
URL url = Loader.getResource(filename, myClassLoader);
if (updateStatus) {
this.statusOnResourceSearch(filename, myClassLoader, url);
}
return url;
}
(3)解析xml日志配置文件,新建SaxEventRecorder解析器,实现默认解析器DefaultHandler,复写startDocument()、startElement()、characters()、endElement()方法,添加Locator locator;属性,将解析的数据存储在saxEventList集合中,根据节点的不同位置,分别为StartEvent、EndEvent、BodyEvent对象
public class SaxEventRecorder extends DefaultHandler implements ContextAware {
final ContextAwareImpl cai;
public List<SaxEvent> saxEventList = new ArrayList();
Locator locator;
ElementPath globalElementPath = new ElementPath();
public SaxEventRecorder(Context context) {
this.cai = new ContextAwareImpl(context, this);
}
public final void recordEvents(InputStream inputStream) throws JoranException {
this.recordEvents(new InputSource(inputStream));
}
public List<SaxEvent> recordEvents(InputSource inputSource) throws JoranException {
SAXParser saxParser = this.buildSaxParser();
try {
saxParser.parse(inputSource, this);
return this.saxEventList;
} catch (IOException var4) {
this.handleError("I/O error occurred while parsing xml file", var4);
} catch (SAXException var5) {
throw new JoranException("Problem parsing XML document. See previously reported errors.", var5);
} catch (Exception var6) {
this.handleError("Unexpected exception while parsing XML document.", var6);
}
throw new IllegalStateException("This point can never be reached");
}
(4)将解析出的数据进行初始化配置,buildInterpreter()方法用来新建Interpreter对象,logback依赖这个类进行初始化工作;play()遍历xml文件中的数据,进行配置
public void doConfigure(List<SaxEvent> eventList) throws JoranException {
this.buildInterpreter();
synchronized(this.context.getConfigurationLock()) {
this.interpreter.getEventPlayer().play(eventList);
}
}
a.RuleStore类添加匹配规则,当xml文件中的节点名称不对时,报错
public void addInstanceRules(RuleStore rs) {
super.addInstanceRules(rs);
rs.addRule(new ElementSelector("configuration"), new ConfigurationAction());
rs.addRule(new ElementSelector("configuration/contextName"), new ContextNameAction());
rs.addRule(new ElementSelector("configuration/contextListener"), new LoggerContextListenerAction());
rs.addRule(new ElementSelector("configuration/insertFromJNDI"), new InsertFromJNDIAction());
rs.addRule(new ElementSelector("configuration/evaluator"), new EvaluatorAction());
rs.addRule(new ElementSelector("configuration/appender/sift"), new SiftAction());
rs.addRule(new ElementSelector("configuration/appender/sift/*"), new NOPAction());
rs.addRule(new ElementSelector("configuration/logger"), new LoggerAction());
rs.addRule(new ElementSelector("configuration/logger/level"), new LevelAction());
rs.addRule(new ElementSelector("configuration/root"), new RootLoggerAction());
rs.addRule(new ElementSelector("configuration/root/level"), new LevelAction());
rs.addRule(new ElementSelector("configuration/logger/appender-ref"), new AppenderRefAction());
rs.addRule(new ElementSelector("configuration/root/appender-ref"), new AppenderRefAction());
rs.addRule(new ElementSelector("*/if"), new IfAction());
rs.addRule(new ElementSelector("*/if/then"), new ThenAction());
rs.addRule(new ElementSelector("*/if/then/*"), new NOPAction());
rs.addRule(new ElementSelector("*/if/else"), new ElseAction());
rs.addRule(new ElementSelector("*/if/else/*"), new NOPAction());
if (PlatformInfo.hasJMXObjectName()) {
rs.addRule(new ElementSelector("configuration/jmxConfigurator"), new JMXConfiguratorAction());
}
rs.addRule(new ElementSelector("configuration/include"), new IncludeAction());
rs.addRule(new ElementSelector("configuration/consolePlugin"), new ConsolePluginAction());
rs.addRule(new ElementSelector("configuration/receiver"), new ReceiverAction());
}
b.针对StartElement对象,调用callBeginAction()方法
private void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
String tagName = this.getTagName(localName, qName);
this.elementPath.push(tagName);
if (this.skip != null) {
this.pushEmptyActionList();
} else {
List<Action> applicableActionList = this.getApplicableActionList(this.elementPath, atts);
if (applicableActionList != null) {
this.actionListStack.add(applicableActionList);
this.callBeginAction(applicableActionList, tagName, atts);
} else {
this.pushEmptyActionList();
String errMsg = "no applicable action for [" + tagName + "], current ElementPath is [" + this.elementPath + "]";
this.cai.addError(errMsg);
}
}
}