目录
一、Logback概述
1.Logback相较于Log4j的优势
2.日志架构
2.1 组成关系
2.2 类结构
二、启动源码分析
1.LoggerFactory
2.StaticLoggerBinder
3.ContextInitializer
4.Configurator
4.1 BasicConfigurator
4.2 GenericConfigurator
4.3 JoranConfigurator
5.SaxEventRecorder
6.EventPlayer
7.Interpreter
8.Action
9.LoggerContext
Logback是Log4j项目的继承者,使用过Log4j对Logback就能很快的上手,其配置方式和使用方式都差不多。Logback一共被分为三部分,logback-core,logback-classic和logback-access,core是Logback的基础核心部分,classic则是Logback对Log4j实现的升级版本,而access则实现了SLF4J接口,为项目切换logging、log4j或Java官方的日志JUL提供了无缝切换。
官方给出的优势点如下:
常用关键部分组件关系如下:
看了这个图可以明显的将其分成四个部分:
主要类组成:
乍一看确实是感觉很杂很乱,但只要沿着实现关系分析,就能够看出来这些类组成基本上是和上那一张图所说的四个部分是一致的,基本上就是那四个部分,只是实现更加细化:
按照老规矩,我们先从唯一能接触到的入口开始分析,即LoggerFactory.getLogger()方法,其部分源码如下:
public final class LoggerFactory {
static final String CODES_PREFIX = "http://www.slf4j.org/codes.html";
static final String NO_STATICLOGGERBINDER_URL =
CODES_PREFIX + "#StaticLoggerBinder";
static final String MULTIPLE_BINDINGS_URL =
CODES_PREFIX + "#multiple_bindings";
static final String NULL_LF_URL = CODES_PREFIX + "#null_LF";
static final String VERSION_MISMATCH =
CODES_PREFIX + "#version_mismatch";
static final String SUBSTITUTE_LOGGER_URL =
CODES_PREFIX + "#substituteLogger";
static final String LOGGER_NAME_MISMATCH_URL =
CODES_PREFIX + "#loggerNameMismatch";
static final String REPLAY_URL = CODES_PREFIX + "#replay";
static final String UNSUCCESSFUL_INIT_URL =
CODES_PREFIX + "#unsuccessfulInit";
static final String UNSUCCESSFUL_INIT_MSG =
"org.slf4j.LoggerFactory in failed state. Original " +
+ "exception was thrown EARLIER. See also " +
UNSUCCESSFUL_INIT_URL;
static final int UNINITIALIZED = 0;
static final int ONGOING_INITIALIZATION = 1;
static final int FAILED_INITIALIZATION = 2;
static final int SUCCESSFUL_INITIALIZATION = 3;
static final int NOP_FALLBACK_INITIALIZATION = 4;
static volatile int INITIALIZATION_STATE = UNINITIALIZED;
static final SubstituteLoggerFactory SUBST_FACTORY =
new SubstituteLoggerFactory();
static final NOPLoggerFactory NOP_FALLBACK_FACTORY =
new NOPLoggerFactory();
static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY =
"slf4j.detectLoggerNameMismatch";
static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";
static boolean DETECT_LOGGER_NAME_MISMATCH =
Util.safeGetBooleanSystemProperty(
DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
static private final String[] API_COMPATIBILITY_LIST =
new String[] { "1.6", "1.7" };
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("", logger.getName(),
autoComputedCallingClass.getName()));
Util.report();
}
}
return logger;
}
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
}
LoggerFactory这个类中只有两个getLogger方法,一个参数是类,一个参数是字符串,当参数是类时最终还是会使用类的名字去调用参数是字符串的方法。最终方法会调用进getIloggerFactory中,在这里面会完成Logback的初始化。
getIloggerFactory方法部分关键源码如下:
public final class LoggerFactory {
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:
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
}
在getILoggerFactory方法中将会首先判断Logback是否已经初始化成功,如果没有初始化则会调用performInitialization方法进行文件属性的绑定以及版本检查。如果已经绑定成功则调用到在类图那里所说的StaticLoggerBinder中间类,进而获取LoggerFacotry。
这里需要注意的是判断是否已经实例化那里,有两层if判断,第一层判断是初次筛选,而同步块里面的if则是判断进入同步块的线程,以避免重复初始化。
绑定方法部分源码如下:
public final class LoggerFactory {
private final static void bind() {
try {
Set staticLoggerBinderPathSet = null;
if (!isAndroid()) {
staticLoggerBinderPathSet =
findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
...
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null &&
msg.contains("...")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
...
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("...", e);
}
}
}
这个方法最重要的是StaticLoggerBinder.getSingleton()语句,其它的都是进行一些前置或后置条件。如findPossibleStaticLoggerBinderPathSet方法这一块是为了在类路径中获得StaticLoggerBinder类,而fixSubstituteLoggers等方法则是为了修正SUBST_FACTORY或其它属性。截止到这,LoggerFactory这个入口类的大致作用便完成了。
其入口关键部分代码如下:
public class StaticLoggerBinder implements LoggerFactoryBinder {
public static String REQUESTED_API_VERSION = "1.7.16";
final static String NULL_CS_URL = CoreConstants.CODES_URL + "#null_CS";
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
private static Object KEY = new Object();
static {
SINGLETON.init();
}
private boolean initialized = false;
private LoggerContext defaultLoggerContext = new LoggerContext();
private final ContextSelectorStaticBinder contextSelectorBinder =
ContextSelectorStaticBinder.getSingleton();
private StaticLoggerBinder() {
defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
}
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
static void reset() {
SINGLETON = new StaticLoggerBinder();
SINGLETON.init();
}
public ILoggerFactory getLoggerFactory() {
if (!initialized) {
return defaultLoggerContext;
}
if (contextSelectorBinder.getContextSelector() == null) {
throw new IllegalStateException("..." + NULL_CS_URL);
}
return contextSelectorBinder.getContextSelector()
.getLoggerContext();
}
}
首先将其getSingleton方法相关的关键成员属性以及方法都看一下,可以看到StaticLoggerBinder 是一个单例对象,其内部有一个默认的LoggerContext对象,默认名字参数便是default,并且还有一个静态代码快,里面直接调用了单例对象的init初始化方法,所以等下我们直接看到init方法中。还有一个方法便是LoggerFactoryBinder接口的getLoggerFactory方法,当这个类初始化后调用这个方法便可以获得ILoggerFactory,从方法返回结果来看,返回的便是LoggerContext。
init方法部分关键代码:
public class StaticLoggerBinder implements LoggerFactoryBinder {
void init() {
try {
try {
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
...
}
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)){
StatusPrinter.printInCaseOfErrorsOrWarnings(
defaultLoggerContext);
}
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Exception t) {
...
}
}
}
可以看到init方法是非常简单的,其主要分两部分,一个部分便是使用ContextInitializer类来具体初始化LoggerContext,第二部分便是对初始化结果进行验证和赋值,如果初始化失败了则调用printInCaseOfErrorsOrWarnings方法来打印失败信息,若成功则将LoggerContext赋值进ContextSelectorStaticBinder对象中,以便getLoggerFactory方法获得调用。同时将initialized标识设置为true,意味着初始化成功。
该类的部分关键代码如下:
public class ContextInitializer {
final public static String GROOVY_AUTOCONFIG_FILE = "logback.groovy";
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";
final LoggerContext loggerContext;
public ContextInitializer(LoggerContext loggerContext) {
this.loggerContext = loggerContext;
}
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
URL url =
findConfigFileURLFromSystemProperties(myClassLoader,
updateStatus);
if (url != null) {
return url;
}
url =
getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
url =
getResource(GROOVY_AUTOCONFIG_FILE,myClassLoader,updateStatus);
if (url != null) {
return url;
}
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
}
看到这个类的成员属性值便可以大致猜到,前面的logback.xml、logback-test.xml等值便是logback默认读取的文件名和地址,而logback.configurationFile就是从系统属性中获取的key。而findURLOfDefaultConfigurationFile方法则将其具体调用的顺序写死在代码中。顺序分别为:logback.configurationFile配置值->logback-test.xml->logback.groovy->logback.xml。
接下来接着上面的调用方法链看到autoConfig方法源码:
public class ContextInitializer {
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("..."), e);
}
} else {
BasicConfigurator basicConfigurator =
new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
}
}
}
public void configureByResource(URL url) throws JoranException {
if (url == null) {
throw new IllegalArgumentException("...");
}
final String urlString = url.toString();
if (urlString.endsWith("groovy")) {
if (EnvUtil.isGroovyAvailable()) {
GafferUtil
.runGafferConfiguratorOn(loggerContext, this, url);
} else {
StatusManager sm = loggerContext.getStatusManager();
sm.add(new ErrorStatus("...", loggerContext));
}
} else if (urlString.endsWith("xml")) {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(loggerContext);
configurator.doConfigure(url);
} else {
throw new LogbackException("...");
}
}
}
可以看到autoConfig方法中的逻辑很简单,判断findURLOfDefaultConfigurationFile方法是否返回了可以读取的URL,如果返回了则调用configureByResource方法,根据URL去读取文件进行配置,而如果URL返回为null,则直接使用Configurator及其实现子类的configure方法对LoggerContext对象进行配置。
configureByResource方法逻辑也很简单,直接判断文件后缀是XML还是Groovy,Groovy配置我们不分析,因此只看到解析XML文件的,最终会调用进JoranConfigurator对象的doConfigure配置方法中。
经过上面的ContextInitializer类方法流程,我们确定了在ContextInitializer类中最终不管如何还是会调用进Configurator这一系列实现类中,因此我们看到Configurator接口以及其主要实现子类中来:
public interface Configurator extends ContextAware {
public void configure(LoggerContext loggerContext);
}
可以看到Configurator接口就一个方法configure,在这里面进行上下文配置。
Configurator的方法configure有一个唯一实现子类BasicConfigurator,其源码如下:
public class BasicConfigurator extends ContextAwareBase
implements Configurator {
public BasicConfigurator() {
}
public void configure(LoggerContext lc) {
addInfo("Setting up default configuration.");
ConsoleAppender ca =
new ConsoleAppender();
ca.setContext(lc);
ca.setName("console");
LayoutWrappingEncoder encoder =
new LayoutWrappingEncoder();
encoder.setContext(lc);
TTLLLayout layout = new TTLLLayout();
layout.setContext(lc);
layout.start();
encoder.setLayout(layout);
ca.setEncoder(encoder);
ca.start();
Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.addAppender(ca);
}
}
可以看到这个方法非常简单,便是创建默认的ROOT节点,并为其设置Appender,Layout等属性,这个方法在ContextInitializer类中如果找不到配置文件将会被调用,因此可以看成是一个保底的实现类方法。至于日志生命周期start等方法便在后续篇幅展开。
JoranConfigurator是GenericConfigurator的子类,因此先看到GenericConfigurator部分源码:
public abstract class GenericConfigurator extends ContextAwareBase {
private BeanDescriptionCache beanDescriptionCache;
protected Interpreter interpreter;
public final void doConfigure(URL url) throws JoranException {
InputStream in = null;
try {
informContextOfURLUsedForConfiguration(getContext(), url);
URLConnection urlConnection = url.openConnection();
urlConnection.setUseCaches(false);
in = urlConnection.getInputStream();
doConfigure(in, url.toExternalForm());
} catch (IOException ioe) {
String errMsg = "Could not open URL [" + url + "].";
addError(errMsg, ioe);
throw new JoranException(errMsg, ioe);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ioe) {
String errMsg = "Could not close input stream";
addError(errMsg, ioe);
throw new JoranException(errMsg, ioe);
}
}
}
}
public final void doConfigure(InputStream inputStream,
String systemId) throws JoranException {
InputSource inputSource = new InputSource(inputStream);
inputSource.setSystemId(systemId);
doConfigure(inputSource);
}
public final void doConfigure(final InputSource inputSource)
throws JoranException {
long threshold = System.currentTimeMillis();
SaxEventRecorder recorder = new SaxEventRecorder(context);
recorder.recordEvents(inputSource);
doConfigure(recorder.saxEventList);
StatusUtil statusUtil = new StatusUtil(context);
if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
addInfo("...");
registerSafeConfiguration(recorder.saxEventList);
}
}
public void doConfigure(final List eventList)
throws JoranException {
buildInterpreter();
synchronized (context.getConfigurationLock()) {
interpreter.getEventPlayer().play(eventList);
}
}
protected void buildInterpreter() {
RuleStore rs = new SimpleRuleStore(context);
addInstanceRules(rs);
this.interpreter = new Interpreter(context, rs,
initialElementPath());
InterpretationContext interpretationContext =
interpreter.getInterpretationContext();
interpretationContext.setContext(context);
addImplicitRules(interpreter);
addDefaultNestedComponentRegistryRules(
interpretationContext.getDefaultNestedComponentRegistry());
}
}
当在ContextInitializer类中获取到了默认的XML文件,将会调用到这里来。虽然这个方法流程共有四个方法,但这四个方法无非是对文件进行另一层的封装以及验证,具体封装顺序为URL->InputStream->inputSource->List
而buildInterpreter方法便是对处理读取的文件标签规则进行构建和初始化,其中有两个方法addInstanceRules和addImplicitRules便需要子类来具体实现某些规则。子类JoranConfigurator将会实现这两个方法,并且在ContextInitializer类中调用的类型也是JoranConfigurator。
具体读取解析XML文件的地方便是在SaxEventRecorder类中完成的,而对解析出来的SaxEvent对象完成Logback的解析读取则是在EventPlayer类中完成的,这些后面再分析。
接下来看到JoranConfigurator类的源码:
abstract public class JoranConfiguratorBase
extends GenericConfigurator {
@Override
protected void addInstanceRules(RuleStore rs) {
rs.addRule(new ElementSelector("configuration/variable"),
new PropertyAction());
rs.addRule(new ElementSelector("configuration/property"),
new PropertyAction());
rs.addRule(
new ElementSelector("configuration/substitutionProperty"),
new PropertyAction());
rs.addRule(new ElementSelector("configuration/timestamp"),
new TimestampAction());
rs.addRule(new ElementSelector("configuration/shutdownHook"),
new ShutdownHookAction());
rs.addRule(new ElementSelector("configuration/define"),
new DefinePropertyAction());
rs.addRule(new ElementSelector("configuration/contextProperty"),
new ContextPropertyAction());
rs.addRule(new ElementSelector("configuration/conversionRule"),
new ConversionRuleAction());
rs.addRule(new ElementSelector("configuration/statusListener"),
new StatusListenerAction());
rs.addRule(new ElementSelector("configuration/appender"),
new AppenderAction());
rs.addRule(
new ElementSelector("configuration/appender/appender-ref"),
new AppenderRefAction());
rs.addRule(new ElementSelector("configuration/newRule"),
new NewRuleAction());
rs.addRule(new ElementSelector("*/param"),
new ParamAction(getBeanDescriptionCache()));
}
@Override
protected void addImplicitRules(Interpreter interpreter) {
NestedComplexPropertyIA nestedComplexPropertyIA =
new NestedComplexPropertyIA(getBeanDescriptionCache());
nestedComplexPropertyIA.setContext(context);
interpreter.addImplicitAction(nestedComplexPropertyIA);
NestedBasicPropertyIA nestedBasicIA =
new NestedBasicPropertyIA(getBeanDescriptionCache());
nestedBasicIA.setContext(context);
interpreter.addImplicitAction(nestedBasicIA);
}
@Override
protected void buildInterpreter() {
super.buildInterpreter();
Map omap =
interpreter.getInterpretationContext().getObjectMap();
omap.put(ActionConst.APPENDER_BAG,
new HashMap>());
}
public InterpretationContext getInterpretationContext() {
return interpreter.getInterpretationContext();
}
}
public class JoranConfigurator
extends JoranConfiguratorBase {
@Override
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());
}
@Override
protected void
addDefaultNestedComponentRegistryRules(
DefaultNestedComponentRegistry registry) {
DefaultNestedComponentRules
.addDefaultNestedComponentRegistryRules(registry);
}
}
可以看到JoranConfigurator和其父类JoranConfiguratorBase基本的作用便是手动添加XML文件读取规则,诸如
我们从前面分析GenericConfigurator类的一系列doConfigure方法时,在针对InputSource参数进行解析时实例化了这个对象,并且把Context对象作为参数传了进去,现在我们来看看这里面具体做了什么。部分源码如下:
public class SaxEventRecorder extends DefaultHandler
implements ContextAware {
final ContextAwareImpl cai;
public SaxEventRecorder(Context context) {
cai = new ContextAwareImpl(context, this);
}
public List saxEventList = new ArrayList();
Locator locator;
ElementPath globalElementPath = new ElementPath();
final public void recordEvents(InputStream inputStream)
throws JoranException {
recordEvents(new InputSource(inputStream));
}
public List recordEvents(InputSource inputSource)
throws JoranException {
SAXParser saxParser = buildSaxParser();
try {
saxParser.parse(inputSource, this);
return saxEventList;
} catch (IOException ie) {
handleError("...", ie);
} catch (SAXException se) {
throw new JoranException("...", se);
} catch (Exception ex) {
handleError("...", ex);
}
throw new IllegalStateException("...");
}
private SAXParser buildSaxParser() throws JoranException {
try {
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setValidating(false);
spf.setNamespaceAware(true);
return spf.newSAXParser();
} catch (Exception pce) {
String errMsg = "Parser configuration error occurred";
addError(errMsg, pce);
throw new JoranException(errMsg, pce);
}
}
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) {
String tagName = getTagName(localName, qName);
globalElementPath.push(tagName);
ElementPath current = globalElementPath.duplicate();
saxEventList.add(new StartEvent(current, namespaceURI, localName,
qName, atts, getLocator()));
}
public void characters(char[] ch, int start, int length) {
String bodyStr = new String(ch, start, length);
SaxEvent lastEvent = getLastEvent();
if (lastEvent instanceof BodyEvent) {
BodyEvent be = (BodyEvent) lastEvent;
be.append(bodyStr);
} else {
if (!isSpaceOnly(bodyStr)) {
saxEventList.add(new BodyEvent(bodyStr, getLocator()));
}
}
}
public void endElement(String namespaceURI, String localName,
String qName) {
saxEventList.add(new EndEvent(namespaceURI, localName, qName,
getLocator()));
globalElementPath.pop();
}
}
本次分析只取了一部分方法,就是在doConfigure一系列调用方法链中调用到的recordEvents方法,在这个方法中我们可以看到构建了一个SAXParser对象,这个对象看过Spring解析Spring的xml就能知道,这是Java官方提供给开发者的读取XML文件工具,只需要调用即可。
但是需要注意的是SAXParser类的parse方法除了InputSource参数还有一个DefaultHandler参数,DefaultHandler参数便是将XML标签内容转变成自己框架内能使用读取的关键。在Logback中对DefaultHandler最重要的实现有三个方法:startElement、characters和endElement。这三个方法分别对应标签头、标签体和标签尾,实际上
在上面获得了saxEventList之后,就需要对这些标签内容进行解析,而Logback解析这些标签属性的地方便是在EventPlayer类中。在GenericConfigurator类中调用的便是这个类的play方法。其部分关键源码如下:
public class EventPlayer {
final Interpreter interpreter;
List eventList;
int currentIndex;
public void play(List aSaxEventList) {
eventList = aSaxEventList;
SaxEvent se;
for (currentIndex = 0; currentIndex < eventList.size();
currentIndex++) {
se = eventList.get(currentIndex);
if (se instanceof StartEvent) {
interpreter.startElement((StartEvent) se);
interpreter.getInterpretationContext().fireInPlay(se);
}
if (se instanceof BodyEvent) {
interpreter.getInterpretationContext().fireInPlay(se);
interpreter.characters((BodyEvent) se);
}
if (se instanceof EndEvent) {
interpreter.getInterpretationContext().fireInPlay(se);
interpreter.endElement((EndEvent) se);
}
}
}
public void addEventsDynamically(List eventList, int offset){
this.eventList.addAll(currentIndex + offset, eventList);
}
}
简单明了,只涉及了这个类中的play方法,在play方法中完成对StartEvent、BodyEvent和EndEvent这三个标签的读取解析,具体的解析方法在Interpreter类中,等下我们再去分析。除了play方法外还有另外一个方法addEventsDynamically,这个方法的作用便是供外部调用添加SaxEvent到这个类当中,以便于后续调用play方法完成解析。
这个类实际上在GenericConfigurator类中就已经被调用过了,如在其中添加各种ruler便是在其实现子类JoranConfigurator中完成的。Interpreter的部分关键源码如下:
public class Interpreter {
private static List EMPTY_LIST = new Vector(0);
final private RuleStore ruleStore;
final private InterpretationContext interpretationContext;
final private ArrayList implicitActions;
final private CAI_WithLocatorSupport cai;
private ElementPath elementPath;
Locator locator;
EventPlayer eventPlayer;
Stack> actionListStack;
ElementPath skip = null;
public void startElement(StartEvent se) {
// EventPlayer首先会调用这个方法,从se中获得具体的属性
setDocumentLocator(se.getLocator());
startElement(se.namespaceURI, se.localName, se.qName,
se.attributes);
}
private void startElement(String namespaceURI, String localName,
String qName, Attributes atts) {
// 获得标签的具体名字,如果localName有则取localName
// 一般而言localName和qName是一致的
String tagName = getTagName(localName, qName);
// 将标签名称放进elementPath中,elementPath里面封装了一个ArrayList
// 这个列表将会记录当前的标签路径
elementPath.push(tagName);
if (skip != null) {
pushEmptyActionList();
return;
}
// 判断刚刚获得的标签路径名称是否符合规则的匹配机制
List applicableActionList =
getApplicableActionList(elementPath, atts);
// 如果匹配则对标签进行下一步操作
if (applicableActionList != null) {
actionListStack.add(applicableActionList);
callBeginAction(applicableActionList, tagName, atts);
} else {
// 如果标签匹配失败则添加空的Action列表进去
pushEmptyActionList();
String errMsg = "...";
cai.addError(errMsg);
}
}
public void characters(BodyEvent be) {
setDocumentLocator(be.locator);
// 获取标签内容
String body = be.getText();
// 使用peek,只获取栈顶元素,而不删除
List applicableActionList = actionListStack.peek();
if (body != null) {
body = body.trim();
if (body.length() > 0) {
// 如果有内容则调用处理body的方法
callBodyAction(applicableActionList, body);
}
}
}
public void endElement(EndEvent endEvent) {
setDocumentLocator(endEvent.locator);
endElement(endEvent.namespaceURI, endEvent.localName,
endEvent.qName);
}
private void endElement(String namespaceURI, String localName,
String qName) {
// 从actionListStack中出栈
List applicableActionList =
(List) actionListStack.pop();
if (skip != null) {
if (skip.equals(elementPath)) {
skip = null;
}
} else if (applicableActionList != EMPTY_LIST) {
// 如果Action列表不为空,则对标签进行处理
callEndAction(applicableActionList,
getTagName(localName, qName));
}
// 将元素路径出栈(删除ArrayList最后一个元素)
elementPath.pop();
}
void callBeginAction(List applicableActionList, String tagName,
Attributes atts) {
if (applicableActionList == null) {
return;
}
// 从Action列表中获取元素
Iterator i = applicableActionList.iterator();
while (i.hasNext()) {
Action action = (Action) i.next();
try {
// 调用不同Action实现子类的begin方法
action.begin(interpretationContext, tagName, atts);
} catch (ActionException e) {
skip = elementPath.duplicate();
cai.addError("...", e);
} catch (RuntimeException e) {
skip = elementPath.duplicate();
cai.addError("...", e);
}
}
}
private void callBodyAction(List applicableActionList,
String body) {
if (applicableActionList == null) {
return;
}
Iterator i = applicableActionList.iterator();
while (i.hasNext()) {
Action action = i.next();
try {
// 调用不同Action子类的body方法
action.body(interpretationContext, body);
} catch (ActionException ae) {
cai.addError("...", ae);
}
}
}
private void callEndAction(List applicableActionList,
String tagName) {
if (applicableActionList == null) {
return;
}
Iterator i = applicableActionList.iterator();
while (i.hasNext()) {
Action action = i.next();
try {
// 调用不同Action子类的end方法
action.end(interpretationContext, tagName);
} catch (ActionException ae) {
cai.addError("...", ae);
} catch (RuntimeException e) {
cai.addError("...", e);
}
}
}
}
这个流程有点复杂,因此对于具体的流程便写在了代码中。
其大致图示流程如下图,关于图片的描述见图片流程即可:
上面已经可以看到对于不同的标签会有对应的Action,因此我们看到主要看到LoggerAction,看看Logger是如何被添加进LoggerContext对象的。LoggerAction部分关键源码如下:
public class LoggerAction extends Action {
public static final String LEVEL_ATTRIBUTE = "level";
boolean inError = false;
Logger logger;
public void begin(InterpretationContext ec, String name,
Attributes attributes) {
inError = false;
logger = null;
LoggerContext loggerContext = (LoggerContext) this.context;
String loggerName = ec.subst(attributes.getValue(NAME_ATTRIBUTE));
if (OptionHelper.isEmpty(loggerName)) {
inError = true;
String aroundLine = getLineColStr(ec);
String errorMsg = "..." + aroundLine;
addError(errorMsg);
return;
}
logger = loggerContext.getLogger(loggerName);
String levelStr = ec.subst(attributes.getValue(LEVEL_ATTRIBUTE));
if (!OptionHelper.isEmpty(levelStr)) {
if (ActionConst.INHERITED.equalsIgnoreCase(levelStr) ||
ActionConst.NULL.equalsIgnoreCase(levelStr)) {
addInfo("...");
logger.setLevel(null);
} else {
Level level = Level.toLevel(levelStr);
addInfo("..." + level);
logger.setLevel(level);
}
}
String additivityStr = ec.subst(attributes
.getValue(ActionConst.ADDITIVITY_ATTRIBUTE));
if (!OptionHelper.isEmpty(additivityStr)) {
boolean additive = OptionHelper.toBoolean(additivityStr, true);
addInfo("..." + additive);
logger.setAdditive(additive);
}
ec.pushObject(logger);
}
}
可以看到流程很简单,从LoggerContext获得logger为name的对象,并设置这个对象的level,因此我们才可以在Logback的日志配置文件里配置对某个包或某个类的单独日志级别。但是这个方法依旧没解决Logger从哪里来的问题,我们在这里面只看到了loggerContext.getLogger()方法直接获得了logger,因此可以暂时推断Logger就是在这里面生成的,只是级别是在LoggerAction设置的。
终于分析到Logback的上下文类了,在这个类里将会解决Logger的生成。其部分关键源码如下:
public class LoggerContext extends ContextBase
implements ILoggerFactory, LifeCycle {
final Logger root;
private int size;
private Map loggerCache;
public LoggerContext() {
super();
this.loggerCache = new ConcurrentHashMap();
this.loggerContextRemoteView = new LoggerContextVO(this);
this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
this.root.setLevel(Level.DEBUG);
loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
initEvaluatorMap();
size = 1;
this.frameworkPackages = new ArrayList();
}
public final Logger getLogger(final Class> clazz) {
return getLogger(clazz.getName());
}
@Override
public final Logger getLogger(final String name) {
// 每个logger一定会有名字,因此会判断
if (name == null) {
throw new IllegalArgumentException("...");
}
// 因为root根节点是一定会存在的,不会存在第二个,因此名字是root直接返回
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
return root;
}
int i = 0;
Logger logger = root;
// 从logger缓存中获取logger(如果有的话)
Logger childLogger = (Logger) loggerCache.get(name);
if (childLogger != null) {
return childLogger;
}
String childName;
while (true) {
// 获取java整体流程的“.”位置,如com.iboxpay.Test
// 会分别获得两个“.”的位置
int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
if (h == -1) {
childName = name;
} else {
childName = name.substring(0, h);
}
i = h + 1;
// 上面的流程会依次获得“.”的位置,如com.iboxpay.Test
// 的h值分别为3和11
synchronized (logger) {
childLogger = logger.getChildByName(childName);
if (childLogger == null) {
// 如果logger原来没有这个子节点则创建一个新的
childLogger = logger.createChildByName(childName);
// 放进缓存中并且size+1
loggerCache.put(childName, childLogger);
incSize();
}
}
// 跳到子节点,继续判断
logger = childLogger;
// 如果com.iboxpay.Test是从12开始的,那么h将会是-1,代表以遍历完成
if (h == -1) {
return childLogger;
}
}
// 如com.iboxpay.Test路径将会创建三个logger,分别是com,com.iboxpay
// 和com.iboxpay.Test,各个路径都会创建对应的logger,因此搭配
// LoggerAction就可以实现对不同的包和类完成精确的级别控制
}
private void incSize() {
size++;
}
}
可以看到其实现了ILoggerFactory接口,因此在LoggerFactory中的getLogger方法中获取到的iLoggerFactory对象实际上就是LoggerContext类型,getLogger方法源码如下:
public final class LoggerFactory {
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
}
因此,对于这个方法而言整个流程将是在getILoggerFactory方法中完成Logback上下文的初始化,而创建Logger将是在iLoggerFactory.getLogger()中完成,接下来我们仔细分析一下创建Logger的流程。
看到LoggerContext类默认构造函数,这里面默认创建了一个Logger,名字为ROOT,默认的日志级别是DEBUG,是不是很熟悉?
接下来看到getLogger方法的流程,其流程分析写在了代码中,看代码即可。
至此,Logback的自启动初始化流程便已完成,经过此流程后便完成了XML文件的读取解析(如果有的话),并且也获得了所有已创建Logger的层级关系。后续再获取Logger时只需要从Logback的缓存中获取即可。