文章目錄
一:logback介紹和基本使用
1.框架組成
logback-core.jar (是其它兩個模塊的基礎模塊)
logback-classic.jar (是log4j的一個 改良版本。此外logback-classic完整實現SLF4J)
logback-access.jar (與Servlet容器集成提供通過Http來訪問日誌的功能)
2. 配置文件和日誌組件說明
1. 配置文件
<!-- 級別從高到低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日誌輸出規則 根據當前ROOT 級別,日誌輸出時,級別高於root默認的級別時 會輸出 -->
<!-- 以下 每個配置的 filter 是過濾掉輸出文件裏面,會出現高級別文件,依然出現低級別的日誌信息,通過filter 過濾只記錄本級別的日誌 -->
<!-- scan 當此屬性設置爲true時,配置文件如果發生改變,將會被重新加載,默認值爲true。 -->
<!-- scanPeriod 設置監測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒。當scan爲true時,此屬性生效。默認的時間間隔爲1分鐘。 -->
<!-- debug 當此屬性設置爲true時,將打印出logback內部日誌信息,實時查看logback運行狀態。默認值爲false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 動態日誌級別 -->
<jmxConfigurator />
<!-- 定義日誌文件 輸出位置 -->
<!-- <property name="log_dir" value="C:/test" />-->
<property name="log_dir" value="/home/hadmin/data/logs/src" />
<!-- 日誌最大的歷史 30天 -->
<property name="maxHistory" value="30" />
<!-- ConsoleAppender 控制檯輸出日誌 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
<!-- 設置日誌輸出格式 -->
%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<!-- ERROR級別日誌 -->
<!-- 滾動記錄文件,先將日誌記錄到指定文件,當符合某個條件時,將日誌記錄到其他文件 RollingFileAppender -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 過濾器,只記錄WARN級別的日誌 -->
<!-- 果日誌級別等於配置級別,過濾器會根據onMath 和 onMismatch接收或拒絕日誌。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 設置過濾級別 -->
<level>ERROR</level>
<!-- 用於配置符合過濾條件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用於配置不符合過濾條件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<!-- 最常用的滾動策略,它根據時間來制定滾動策略.既負責滾動也負責出發滾動 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日誌輸出位置 可相對、和絕對路徑 -->
<fileNamePattern>
${log_dir}/error/%d{yyyy-MM-dd}/error-log.log
</fileNamePattern>
<!-- 可選節點,控制保留的歸檔文件的最大數量,超出數量就刪除舊文件假設設置每個月滾動,且<maxHistory>是6, 則只保存最近6個月的文件,刪除之前的舊文件。注意,刪除舊文件是,那些爲了歸檔而創建的目錄也會被刪除 -->
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>
<!-- 設置日誌輸出格式 -->
%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<!-- WARN級別日誌 appender -->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 過濾器,只記錄WARN級別的日誌 -->
<!-- 果日誌級別等於配置級別,過濾器會根據onMath 和 onMismatch接收或拒絕日誌。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 設置過濾級別 -->
<level>WARN</level>
<!-- 用於配置符合過濾條件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用於配置不符合過濾條件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日誌輸出位置 可相對、和絕對路徑 -->
<fileNamePattern>${log_dir}/warn/%d{yyyy-MM-dd}/warn-log.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- INFO級別日誌 appender -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/info/%d{yyyy-MM-dd}/info-log.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- DEBUG級別日誌 appender -->
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/debug/%d{yyyy-MM-dd}/debug-log.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- TRACE級別日誌 appender -->
<appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>TRACE</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/trace/%d{yyyy-MM-dd}/trace-log.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- root級別 DEBUG -->
<root>
<!-- 打印debug級別日誌及以上級別日誌 -->
<level value="debug" />
<!-- 控制檯輸出 -->
<appender-ref ref="console" />
<!-- 文件輸出 -->
<appender-ref ref="ERROR" />
<appender-ref ref="INFO" />
<appender-ref ref="WARN" />
<appender-ref ref="DEBUG" />
<appender-ref ref="TRACE" />
</root>
</configuration>
2.組件
1 .logger組件
Logger作爲日誌的記錄器,把它關聯到應用的對應的logContext上後,主要用於存放日誌信息,也可以定義日誌類型、級別
2. appender
指定日誌輸出的目的地:目的地可以是控制檯、文件、遠程套接字服務器、 數據庫、 JMS和遠程UNIX Syslog守護進程等。
3. Layout
負責把事件轉換成字符串,格式化的日誌信息的輸出
4. loggerContext(上下文)
實現了slf4j的ILoggerFactory,負責製造並存儲logger(以樹結構排列各logger).getLogger方法以 logger名稱爲參數。用同一名字調用LoggerFactory.getLogger 方法所得到的永遠都是同一個logger對象的引用。
5.有效級別及級別的繼承
Logger 可以被分配級別。級別包括:TRACE、DEBUG、INFO、WARN 和 ERROR,定義於ch.qos.logback.classic.Level類。如果 logger沒有被分配級別,那麼它將從有被分配級別的最近的祖先那裏繼承級別。root logger 默認級別是 DEBUG
打印方法的級別大於日誌的有效級別纔會記錄日誌
3.使用演示
1. 引用logback的pom座標:
<!--日誌門面-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!--日誌實現-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
2.創建配置文件
配置文件只支持以下幾種方式:優先級依次減小
方式一:在jvm系統變量中指定路徑(logback.configurationFile)
System.getProperty(“logback.configurationFile”)獲取路徑
方式二:classpath下配置:logback.groovy
方式三:classpath下配置logback-test.xml
方式四:classpath下配置logback.xml ss
默認路徑是classpath根路徑
配置文件內容:
點擊參考樣例
3.使用
Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("test log");
4.工程結構
二:logback原理
從日誌框架的入口開始
Logger logger = LoggerFactory.getLogger(Test.class);
跟進:LoggerFactory的getlogger方法
public static Logger getLogger(Class<?> clazz) {
// 內部會找到logback的實現
// 初始化logback
// 從logcontext獲取logger
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(clazz.getName());
第一行: 找到logback並初始化logback,創建logcontext(實現了ILoggerFactory)
第二行:獲取loggger
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
繼續跟進getILoggerFactory();
2個作用:
初始化logback並更改狀態
根據初始化完成的狀態,返回對應的ILoggerFactory
public static ILoggerFactory getILoggerFactory() {
// 加鎖初始化
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
// 根據初始化狀態,返回對應的ILoggerFactory
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");
}
繼續跟進初始化: performInitialization()
private final static void performInitialization() {
bind(); // 重點
// 檢查jdk版本
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
跟進: bind(),這個是真正執行初始化的地方
1;slf4j找到logback:關鍵在於 staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
2:logback初始化:關鍵在於
StaticLoggerBinder.getSingleton();
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);
}
}
1. slf4j找到logback的實現
跟進:staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
很明顯:通過classloader來搜索指定類(org/slf4j/impl/StaticLoggerBinder.class)的url,如果有多種實現,也會一起讀取到,所有返回Set,如果有多個實現,會打錯誤日誌(Class path contains multiple SLF4J bindings.)但是不影響初始化和後續使用
當執行到 StaticLoggerBinder.getSingleton();會加載StaticLoggerBinder,如果有多個,具體加載哪個看jvm的加載策略,每一個都有可能,一旦加載了一個,其它的就不會加載了
2. logback初始化
跟進 StaticLoggerBinder.getSingleton();看logback初始化
現來看看StaticLoggerBinder的結構:
當執行StaticLoggerBinder.getSingleton();的時候會先加載StaticLoggerBinder,執行類初始化,加載static成員,以上標識紅色的部分
先執行: private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();實例初始化
初始化實例成員變量:
private boolean initialized = false;
private LoggerContext defaultLoggerContext = new LoggerContext();
private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
defaultLoggerContext 創建完成
之後執行構造器:給defaultLoggerContext設置默認的名子default
private StaticLoggerBinder() {
defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
}
再執行:(重點)
static {
SINGLETON.init();
}
void init() {
try {
try {
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 (Throwable t) {
// we should never get here
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}
logcontext初始化工具類
new ContextInitializer(defaultLoggerContext),具體實現在autoConfig();
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);
}
}
}
首先 從 URL url = findURLOfDefaultConfigurationFile(true);中讀取配置文件
如果有配置文件:解析流程如下
public void configureByResource(URL url) throws JoranException {
if (url == null) {
throw new IllegalArgumentException("URL argument cannot be null");
}
final String urlString = url.toString();
if (urlString.endsWith("groovy")) {
if (EnvUtil.isGroovyAvailable()) {
// avoid directly referring to GafferConfigurator so as to avoid
// loading groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
GafferUtil.runGafferConfiguratorOn(loggerContext, this, url);
} else {
StatusManager sm = loggerContext.getStatusManager();
sm.add(new ErrorStatus("Groovy classes are not available on the class path. ABORTING INITIALIZATION.", loggerContext));
}
} else if (urlString.endsWith("xml")) {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(loggerContext);
configurator.doConfigure(url);
} else {
throw new LogbackException("Unexpected filename extension of file [" + url.toString() + "]. Should be either .groovy or .xml");
}
}
重點關注這幾行解析xml的
// 創建配置解析類
JoranConfigurator configurator = new JoranConfigurator();
// 設置上下文
configurator.setContext(loggerContext);
// 解析配置
configurator.doConfigure(url);
// 解析配置
public final void doConfigure(URL url) throws JoranException {
InputStream in = null;
try {
informContextOfURLUsedForConfiguration(getContext(), url);
URLConnection urlConnection = url.openConnection();
// per http://jira.qos.ch/browse/LBCORE-105
// per http://jira.qos.ch/browse/LBCORE-127
urlConnection.setUseCaches(false);
in = urlConnection.getInputStream();
doConfigure(in);
} 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(final InputSource inputSource) throws JoranException {
long threshold = System.currentTimeMillis();
// if (!ConfigurationWatchListUtil.wasConfigurationWatchListReset(context)) {
// informContextOfURLUsedForConfiguration(getContext(), null);
// }
SaxEventRecorder recorder = new SaxEventRecorder(context);
recorder.recordEvents(inputSource);
doConfigure(recorder.saxEventList);
// no exceptions a this level
StatusUtil statusUtil = new StatusUtil(context);
if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
addInfo("Registering current configuration as safe fallback point");
registerSafeConfiguration(recorder.saxEventList);
}
}
以下三行代碼解析xml
SaxEventRecorder recorder = new SaxEventRecorder(context);
recorder.recordEvents(inputSource);
doConfigure(recorder.saxEventList);
SaxEventRecorder繼承DefaultHandler,重寫了startDocument,endDocument,startElement,endElement,characters幾個重要方法
在 recorder.recordEvents(inputSource);中把xml配置文件解析成對應的SaxEvent
在解析過程中,通過SAX的方式解析,每一個元素解析過程中會調用SaxEventRecorder的startelement,characters,endElement方法,可以看到都是在封裝SaxEvent
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 endElement(String namespaceURI, String localName, String qName) {
saxEventList.add(new EndEvent(namespaceURI, localName, qName, getLocator()));
globalElementPath.pop();
}
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 {
// ignore space only text if the previous event is not a BodyEvent
if (!isSpaceOnly(bodyStr)) {
saxEventList.add(new BodyEvent(bodyStr, getLocator()));
}
}
}
跟進 doConfigure(recorder.saxEventList);
public void doConfigure(final List<SaxEvent> eventList) throws JoranException {
buildInterpreter();
// disallow simultaneous configurations of the same context
synchronized (context.getConfigurationLock()) {
interpreter.getEventPlayer().play(eventList);
}
}
buildInterpreter();創建一個解析器解析SaxEvent
protected void buildInterpreter() {
// 首先構造規則集合: 元素和Action的對應關係
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());
}
構造RuleStore
// 解析SaxEvent
interpreter.getEventPlayer().play(eventList);
顯然在遍歷SaxEventlist,分爲三種StartEvent,BodyEvent,EndEvent
一旦匹配成功就從event中取出元素信息,然後從RuleStore中尋找對應的action,執行對應的action的satrt,body,end方法完成解析
public void play(List<SaxEvent> aSaxEventList) {
eventList = aSaxEventList;
SaxEvent se;
for (currentIndex = 0; currentIndex < eventList.size(); currentIndex++) {
se = eventList.get(currentIndex);
if (se instanceof StartEvent) {
interpreter.startElement((StartEvent) se);
// invoke fireInPlay after startElement processing
interpreter.getInterpretationContext().fireInPlay(se);
}
if (se instanceof BodyEvent) {
// invoke fireInPlay before characters processing
interpreter.getInterpretationContext().fireInPlay(se);
interpreter.characters((BodyEvent) se);
}
if (se instanceof EndEvent) {
// invoke fireInPlay before endElement processing
interpreter.getInterpretationContext().fireInPlay(se);
interpreter.endElement((EndEvent) se);
}
}
}
例如:
以解析logger元素爲例,看如何從SaxEvent找到LoggerAction執行,創建logger,存到logggerContext下
開始解析到logger的StarEvent事件
private void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
// 獲取元素名稱
String tagName = getTagName(localName, qName);
// 元素名稱入棧,後面解析EndEvfent時出棧
elementPath.push(tagName);
if (skip != null) {
// every startElement pushes an action list
pushEmptyActionList();
return;
}
List<Action> applicableActionList = getApplicableActionList(elementPath, atts);
if (applicableActionList != null) {
actionListStack.add(applicableActionList);
callBeginAction(applicableActionList, tagName, atts);
} else {
// every startElement pushes an action list
pushEmptyActionList();
String errMsg = "no applicable action for [" + tagName + "], current ElementPath is [" + elementPath + "]";
cai.addError(errMsg);
}
}
在 List applicableActionList = getApplicableActionList(elementPath, atts);中獲取event對應的Action
actionListStack.add(applicableActionList);將Action入棧後面會用到(解析endevent的時候會出棧)
// 調用action的方法
callBeginAction(applicableActionList, tagName, atts);
最終執行
action.begin(interpretationContext, tagName, atts);
即:
LoggerAction的begin方法
public void begin(InterpretationContext ec, String name, Attributes attributes) {
// Let us forget about previous errors (in this object)
inError = false;
logger = null;
// 獲取上下文
LoggerContext loggerContext = (LoggerContext) this.context;
// 獲取logger元素的name屬性
String loggerName = ec.subst(attributes.getValue(NAME_ATTRIBUTE));
if (OptionHelper.isEmpty(loggerName)) {
inError = true;
String aroundLine = getLineColStr(ec);
String errorMsg = "No 'name' attribute in element " + name + ", around " + 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("Setting level of logger [" + loggerName + "] to null, i.e. INHERITED");
logger.setLevel(null);
} else {
Level level = Level.toLevel(levelStr);
addInfo("Setting level of logger [" + loggerName + "] to " + level);
logger.setLevel(level);
}
}
// 設置additivity屬性
String additivityStr = ec.subst(attributes.getValue(ActionConst.ADDITIVITY_ATTRIBUTE));
if (!OptionHelper.isEmpty(additivityStr)) {
boolean additive = OptionHelper.toBoolean(additivityStr, true);
addInfo("Setting additivity of logger [" + loggerName + "] to " + additive);
logger.setAdditive(additive);
}
ec.pushObject(logger);
}
additivity作用在於 children-logger是否使用 rootLogger配置的appender進行輸出。
false:表示只用當前logger的appender-ref。
true:表示當前logger的appender-ref和rootLogger的appender-ref都有效
logger = loggerContext.getLogger(loggerName);這一行獲取logger
如果已經創建同名的直接返回,否則創建對象,創建的時候可能創建多個
例如com.test.TestLog爲名的logger元素會創建三個logger
com和com.test和com.test.TestLog爲名形成一個鏈狀結構(父子結構,root是所有logger的父logger)
ec.pushObject(logger);把創建的logger放到InterpretationContext中後面會用到
接着會解析到appender-ref元素的StartEvent
AppenderRefAction對應的begin方法
public void begin(InterpretationContext ec, String tagName, Attributes attributes) {
// Let us forget about previous errors (in this object)
inError = false;
// logger.debug("begin called");
// 在解析logger的startevent時,在LoggerAction最後 ec.pushObject(logger);所以現在取出的就是logger
Object o = ec.peekObject();
// logger的確是AppenderAttachable的實現類,所以該分支代碼不執行
if (!(o instanceof AppenderAttachable)) {
String errMsg = "Could not find an AppenderAttachable at the top of execution stack. Near [" + tagName + "] line " + getLineNumber(ec);
inError = true;
addError(errMsg);
return;
}
AppenderAttachable<E> appenderAttachable = (AppenderAttachable<E>) o;
// 獲取當前appender-ref元素的ref屬性引用的appder名稱
String appenderName = ec.subst(attributes.getValue(ActionConst.REF_ATTRIBUTE));
if (OptionHelper.isEmpty(appenderName)) {
// print a meaningful error message and return
String errMsg = "Missing appender ref attribute in <appender-ref> tag.";
inError = true;
addError(errMsg);
return;
}
// 從InterpretationContext中獲取key爲APPENDER_BAG的一個Map()
HashMap<String, Appender<E>> appenderBag = (HashMap<String, Appender<E>>) ec.getObjectMap().get(ActionConst.APPENDER_BAG);
// 從APPENDER_BAG中獲取前面ref指定的apper,這些apppder是在解析appender的startevent時加進去的(AppenderAction的begin方法中)
Appender<E> appender = (Appender<E>) appenderBag.get(appenderName);
if (appender == null) {
String msg = "Could not find an appender named [" + appenderName + "]. Did you define it below instead of above in the configuration file?";
inError = true;
addError(msg);
addError("See " + CoreConstants.CODES_URL + "#appender_order for more details.");
return;
}
// 將apper加入到logger中
addInfo("Attaching appender named [" + appenderName + "] to " + appenderAttachable);
appenderAttachable.addAppender(appender);
}
從以下可以看出Logger是AppenderAttachable的實現類
接着解析到apper-ref的結束事件,endevent,對應了AppenderRefAction的end方法,什麼也沒做,接着解析到logger的endwvent,對應LoggerAction的end方法,從InterpretationContext中出棧並且刪除
到此一個logger元素解析完成
3. 獲取logger
在logback中就是從LoggerContext的loggerCache中取出指定名稱的logger