承接前文log4j源码解析,前文主要介绍了log4j的文件加载方式以及Logger对象创建。本文将在此基础上具体看下log4j是如何解析文件并输出我们所常见的日志格式

附例

文件的加载方式,我们就选举log4j.properties作为分析的文件例子,并附上相应的通用配置

log4j.rootLogger=info,stdout,logfile,errorfile

log4j.logger.org.apache=DEBUG
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.freemarker.core=error #standout log appender #
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n #common log appender #
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.File=../logs/appender-test/info.log
log4j.appender.logfile.append=true
log4j.appender.logfile.encoding=GB18030
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n #error log appender #
log4j.appender.errorfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.errorfile.File=../logs/appender-test/error.log
log4j.appender.errorfile.Threshold=WARN
log4j.appender.errorfile.append=true
log4j.appender.errorfile.encoding=GB18030
log4j.appender.errorfile.layout=org.apache.log4j.PatternLayout
log4j.appender.errorfile.layout.ConversionPattern=%d %p [%c] - %m%n

此处不详解,我们直接看源码方面是如何处理,从代码层面来通用理解下上述的配置

OptionConverter

操作log4j配置的主要工具类,所有的读取配置并封装成对象均以此类作为入口,其在LogManager的调用方式为

OptionConverter.selectAndConfigure(url,configuratorClassName,LogManager.getLoggerRepository());

入参作下简单的展示

url - 文件路径

clazz - log4j.configuratorClass属性对应的class,默认为null

hierarchy - log4j的层级管理类,存储log4j的通用配置,默认为Hierarchy类

OptionConverter#selectAndConfigure-解析配置入口

简单看下源码

static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
Configurator configurator = null;
String filename = url.getFile();
//xml格式的文件则采用DOMConfigurator解析类,表明默认采用xml格式的解析方式
if(clazz == null && filename != null && filename.endsWith(".xml")) {
clazz = "org.apache.log4j.xml.DOMConfigurator";
} if(clazz != null) {
LogLog.debug("Preferred configurator class: " + clazz);
configurator = (Configurator) instantiateByClassName(clazz,
Configurator.class,
null);
if(configurator == null) {
LogLog.error("Could not instantiate configurator ["+clazz+"].");
return;
}
} else {
//最后一种方式则为properties解析方式
configurator = new PropertyConfigurator();
} configurator.doConfigure(url, hierarchy);
}

从简单的注释中我们可以得出log4j只支持两种方式的解析方式

1.DOMConfigurator-xml格式的解析器,默认

2.PropertyConfigurator-properties格式的解析器

本文则着重讲解.properties配置文件的解析,即关注PropertyConfigurator解析器

PropertyConfigurator#doConfigure-解析properties配置文件

读取文件的方式就不分析了,很常见的采用Properties类来存储数据,递上重要的逻辑片段代码

public void doConfigure(Properties properties, LoggerRepository hierarchy) {
repository = hierarchy;
// 读取log4j.debug配置,值为boolean型,表明内部log是否支持debug模式
String value = properties.getProperty(LogLog.DEBUG_KEY);
if(value == null) {
value = properties.getProperty("log4j.configDebug");
if(value != null)
LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
}
if(value != null) {
LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
} //读取log4j.reset的boolean值,true代表使用默认的配置
String reset = properties.getProperty(RESET_KEY);
if (reset != null && OptionConverter.toBoolean(reset, false)) {
hierarchy.resetConfiguration();
} //log4j.threshold阈值配置,也就是告警级别配置
String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
properties);
if(thresholdStr != null) {
hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
(Level) Level.ALL));
LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
} // 配置根分类,也就是rootLogger
configureRootCategory(properties, hierarchy);
// 配置Logger工厂
configureLoggerFactory(properties);
// 解析非root的其他配置
parseCatsAndRenderers(properties, hierarchy); LogLog.debug("Finished configuring.");
// 清空下缓存
registry.clear();
}

按照上面的解析顺序作下备注

1.解析log4j.debug/log4j.configDebug(boolean)

是否让log4j的内部输出源支持debug模式,其实也就是是否调用System.out.println()方法,默认不支持debug模式,支持warn/error模式。(支持System.err.println()方法)

2.解析log4j.reset(boolean)

是否重新设置log4j配置,默认不重新设置(Optional,作用微小)

3.解析log4j.threshold(String)

trace/debug/info/warn/error/fatal配置告警级别,表明对所有的输出源,低于该等级则不输出

4.解析根节点rootLogger

5.解析日志工厂

6.解析非根节点

我们对后三步的操作作下简单的分析,加深我们对通用配置的理解

PropertyConfigurator#configureRootCategory-解析根节点

首先简单的看下里面的操作逻辑

void configureRootCategory(Properties props, LoggerRepository hierarchy) {
// log4j.rootLogger或者log4j.rootCategory,支持${}系统变量取值
String effectiveFrefix = ROOT_LOGGER_PREFIX;
String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props); if(value == null) {
value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
effectiveFrefix = ROOT_CATEGORY_PREFIX;
} if(value == null)
LogLog.debug("Could not find root logger information. Is this OK?");
else {
Logger root = hierarchy.getRootLogger();
synchronized(root) {
// 关键代码
parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
}
}
}

承接上述的关键代码分析,此处的logger参数为rootLogger

/**
** This method must work for the root category as well.
*/
void parseCategory(Properties props, Logger logger, String optionKey,
String loggerName, String value) {
LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
// ,分隔符解析
StringTokenizer st = new StringTokenizer(value, ","); if(!(value.startsWith(",") || value.equals(""))) { if(!st.hasMoreTokens())
return; String levelStr = st.nextToken();
LogLog.debug("Level token is [" + levelStr + "]."); // If the level value is inherited, set category level value to
// null. We also check that the user has not specified inherited for the
// root category.
if(INHERITED.equalsIgnoreCase(levelStr) ||
NULL.equalsIgnoreCase(levelStr)) {
if(loggerName.equals(INTERNAL_ROOT_NAME)) {
LogLog.warn("The root logger cannot be set to null.");
} else {
logger.setLevel(null);
}
} else {
logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
}
LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
} // 删除所有的输出源对象
logger.removeAllAppenders(); Appender appender;
String appenderName;
while(st.hasMoreTokens()) {
appenderName = st.nextToken().trim();
if(appenderName == null || appenderName.equals(","))
continue;
LogLog.debug("Parsing appender named \"" + appenderName +"\".");
appender = parseAppender(props, appenderName);
if(appender != null) {
logger.addAppender(appender);
}
}
}

由以上的代码可以简单的得知log4j.rootLogger对应的配置项为{level},{appenderNames}

1.{level} - 日志等级,设置根日志的日志等级,应用于所有的输出源

2.{appenderNames} - 可配置多个输出源,以,为分隔符。并由此属性解析log4j.appender开头的配置项

再而分析了解下PropertyConfigurator#parseAppender()方法解析输出源,为了防止代码展示过多,我们截取主要的代码片段进行分析

Appender parseAppender(Properties props, String appenderName) {
....
// log4j.appender.{appenderName}
String prefix = APPENDER_PREFIX + appenderName;
// log4j.appender.{appenderName}.layout
String layoutPrefix = prefix + ".layout";
// 首先根据log4j.appender.{appenderName}解析得到Appender对象
appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
org.apache.log4j.Appender.class,
null);
... if(appender instanceof OptionHandler) {
if(appender.requiresLayout()) {
// 解析得到Layout对象,代表该输出源的输出格式
Layout layout = (Layout) OptionConverter.instantiateByKey(props,
layoutPrefix,
Layout.class,
null);
....
// 解析log4j.appender.{appenderName}.errorhandler
final String errorHandlerPrefix = prefix + ".errorhandler";
String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
if (errorHandlerClass != null) {
ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
errorHandlerPrefix,
ErrorHandler.class,
null);
if (eh != null) {
appender.setErrorHandler(eh);
LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\".");
// 解析ErrorHandler对象
parseErrorHandler(eh, errorHandlerPrefix, props, repository);
...
} }
...
}
// 解析log4j.appender.{appenderName}.filter配置
parseAppenderFilters(props, appenderName, appender);
registryPut(appender);
return appender;
}

具体解析的过程就不讲解了,只在此处作下罗列,此处假定appenderName为console

1.log4j.appender.console - 对应的输出源的类名

2.log4j.appender.console.layout - 对应输出源的日志展示类名,通用为log4j.appender.{appenderName}.layout

3.log4j.appender.console.errorhandler-对应输出源的错误信息处理类名

4.log4j.appender.console.filter-输出源过滤类,支持配置多个。格式为log4j.appender.console.filter.{filterName}={filterClass}

5.log4j.appender.console.encoding/threshold-此类的额外参数,其会利用反射的机制调用相应的setter方法进行设置

PropertyConfigurator#configureLoggerFactory-解析日志工厂

解析的为log4j.loggerFactory配置,其可以指定logger工厂的实现类,默认为DefaultCategoryFactory,其内部就一个方法makeNewLoggerInstance()用于创建日志类Logger。用户可自定义实现

PropertyConfigurator#parseCatsAndRenderers-解析非根节点

解析的为log4j.logger/log4j.category/log4j.additivity/log4j.renderer/log4j.throwableRenderer配置,具体解析读者可自行分析,此处作下总结

1.log4j.logger/log4j.category以此为开头的配置,其会解析为Logger对象,方式与log4j.rootLogger配置一致,多用于对指定的类进行特定级别的输出,默认继承根Logger对象的输出源配置

2.log4j.additivity以此开头的配置,表明对特定的Logger对象只输出自己所拥有的Appenders,不采用根Logger对象的Appenders

3.log4j.renderer/log4j.throwableRenderer开头的配置,前者主要配置对普通输出信息的渲染处理,后者对异常信息的渲染处理。默认均有实现,一般不指定

4.对输出源的日志级别输出与否比较规则作下总结(以基于com.jing.test.Application类调用info()方法输出举个例子):

  • 首先判断是否>=log4j.threshold属性指定的日志级别Level,如果不满足,则不输出,反之继续往下走。eg.log4j.threshold=WARN则输出源无法输出

  • 然后根据loggerName获取log4j.category/log4j.logger对应的Level(如果loggerName以.分隔,则当前loggerName找不到会向父级获取,没定义则应用rootLogger),判断是否>=Level,否则不输出,反之继续往下走

    eg. 比如定义了log4j.logger.com.jing.test=INFO,但没定义log4j.logger.com.jing.test.Application则其会应用com.jing.test中的Level,即Level=INFO

  • 最后获取输出源Appender指定的日志级别Level,即${appenderName}.Threshold属性,如果>=指定的Level,则进行输出,反之不输出

总结

见本文的分析,通过源码加深我们对配置的理解,心中多一份踏实

log4j源码解析-文件解析的更多相关文章

  1. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  2. InfluxDB源码目录结构解析

    操作系统 : CentOS7.3.1611_x64 go语言版本:1.8.3 linux/amd64 InfluxDB版本:1.1.0 influxdata主目录结构 [root@localhost ...

  3. Spring源码入门——DefaultBeanNameGenerator解析 转发 https://www.cnblogs.com/jason0529/p/5272265.html

    Spring源码入门——DefaultBeanNameGenerator解析   我们知道在spring中每个bean都要有一个id或者name标示每个唯一的bean,在xml中定义一个bean可以指 ...

  4. 鸿蒙内核源码分析(ELF解析篇) | 你要忘了她姐俩你就不是银 | 百篇博客分析OpenHarmony源码 | v53.02

    百篇博客系列篇.本篇为: v53.xx 鸿蒙内核源码分析(ELF解析篇) | 你要忘了她姐俩你就不是银 | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应 ...

  5. v72.01 鸿蒙内核源码分析(Shell解析) | 应用窥伺内核的窗口 | 百篇博客分析OpenHarmony源码

    子曰:"苟正其身矣,于从政乎何有?不能正其身,如正人何?" <论语>:子路篇 百篇博客系列篇.本篇为: v72.xx 鸿蒙内核源码分析(Shell解析篇) | 应用窥视 ...

  6. 笔记-twisted源码-import reactor解析

    笔记-twisted源码-import reactor解析 1.      twisted源码解析-1 twisted reactor实现原理: 第一步: from twisted.internet ...

  7. java 日志体系(四)log4j 源码分析

    java 日志体系(四)log4j 源码分析 logback.log4j2.jul 都是在 log4j 的基础上扩展的,其实现的逻辑都差不多,下面以 log4j 为例剖析一下日志框架的基本组件. 一. ...

  8. [C#] .NET Core项目修改project.json来引用其他目录下的源码等文件的办法 & 解决多框架时 project.json 与 app.config冲突的问题

    作者: zyl910 一.缘由 项目规模大了后,经常会出现源码文件分布在不同目录的情况,但.NET Core项目默认只有项目目录下的源码文件,且不支持"Add As Link"方式 ...

  9. .NET Core项目修改project.json来引用其他目录下的源码等文件的办法 & 解决多框架时 project.json 与 app.config冲突的问题

    作者: zyl910 一.缘由 项目规模大了后,经常会出现源码文件分布在不同目录的情况,但.NET Core项目默认只有项目目录下的源码文件,且不支持“Add As Link”方式引入文件.这时需要手 ...

随机推荐

  1. hbmy周赛1--B

    B - 改革春风吹满地 Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit  ...

  2. 【Keras】基于SegNet和U-Net的遥感图像语义分割

    上两个月参加了个比赛,做的是对遥感高清图像做语义分割,美其名曰"天空之眼".这两周数据挖掘课期末project我们组选的课题也是遥感图像的语义分割,所以刚好又把前段时间做的成果重新 ...

  3. iOS项目——项目开发环境搭建

    在开发项目之前,我们需要做一些准备工作,了解iOS扩展--Objective-C开发编程规范是进行开发的必备基础,学习iOS学习--Xcode9上传项目到GitHub是我们进行版本控制和代码管理的选择 ...

  4. JEECG 3.7.1 版本发布,企业级JAVA快速开发平台

    JEECG 3.7.1 版本发布,企业级JAVA快速开发平台 ---------------------------------------- Version:  Jeecg_3.7.1项 目:   ...

  5. in运算符(javascript)

    in的用法,如x in y: 1.如果第二个运算数为对象,则in运算符用来检测第一个运算数是否是第二个运算数的属性名.是,返回true,否则返回false. 例: var obj = {x:1,y:2 ...

  6. (一)surging 微服务框架使用系列之surging 的准备工作rabbitmq安装

    (1)下载erlang: http://www.erlang.org/download/otp_win64_17.3.exe 并安装 (2)下载RabbitMQ: http://www.rabbitm ...

  7. file_get_contents("php://input")的使用方法

    $data = file_get_contents("php://input"); //input 是个可以访问请求的原始数据的只读流. POST 请求的情况下,最好使用 php: ...

  8. 如何把本地项目上传到Github

    作为一个有追求的程序员,需要撸点自己的开源项目,虽然我现在只是在学着造轮子,但这并不影响我成为大神的心. Github是基于git实现的代码托管,很多程序员在上面托管自己的开源项目,我使用Github ...

  9. 怎样实现给DEDE的栏目增加栏目图片(2)

    2.3 打开dede/templets/catalog_edit.htm页面,查找 栏目名称:

  10. css cursor属性-显示的光标的类型(形状)的用法和定义

    在网页布局的时候,在特定的地方,光标形状各有区别.这个时候,就需要用到css的cursor属性.根据自身需要选择设置鼠标指针样式. 定义和用法 cursor 属性规定要显示的光标的类型(形状). 该属 ...