文章目录
一: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