之前看了Log4J是如何匹配记录的log等级的的源码,log4J的配置项有很多,要一一的去进行查看log4J是如何应用这些配置项的,今天这篇记录下log4J是如何匹配记录的格式,先上配置,这里展示的是我自己重写的
<appender name="DailyRollingFile" class="com.log.jlogstash.logger.DailySizeRollingFileAppender">
<param name="File" value="${LogDir}/${svr}/${Seq}"/>
<param name="DirectoryPattern" value="yyyyMMdd"/>
<param name="DatePattern" value="'_'yyyyMMddHH'_%02d.log'"/>
<param name="Append" value="true" />
<param name="MaxFileSize" value="20MB" />
<param name="MaxBackupIndex" value="50" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n" />
</layout>
</appender>
实际如果不使用自己重写的的话,可以直接使用log4J提供的appender,如下,具体可以根据自身情况选择
<appender name="DailyRollingFile" class="org.apache.log4j.RollingFileAppender">
<param name="File" value="${LogDir}/${svr}/${Seq}"/>
<param name="DirectoryPattern" value="yyyyMMdd"/>
<param name="DatePattern" value="'_'yyyyMMddHH'_%02d.log'"/>
<param name="Append" value="true" />
<param name="MaxFileSize" value="20MB" />
<param name="MaxBackupIndex" value="50" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n" />
</layout>
</appender>
关于记录log的格式,配置在appender的layout参数中,具体的含义可以自行在网上搜索,我每次用的时候都是去找,因为记不住
那Log4J是怎么怎么使用这个Layout参数的勒?围绕这个问题,还是得回到LogManager的加载过程
在LogManager的加载过程中,会解析文件中的appender,会调用DomConfigurator这个类下的parseAppender的方法将配置文件中的参数加载到appender里面,如下源码
protected Appender parseAppender(Element appenderElement) {
String className = this.subst(appenderElement.getAttribute("class"));
LogLog.debug("Class name: [" + className + ']');
try {
Object instance = Loader.loadClass(className).newInstance();
Appender appender = (Appender)instance;
PropertySetter propSetter = new PropertySetter(appender);
appender.setName(this.subst(appenderElement.getAttribute("name")));
NodeList children = appenderElement.getChildNodes();
int length = children.getLength();
for(int loop = 0; loop < length; ++loop) {
Node currentNode = children.item(loop);
if (currentNode.getNodeType() == 1) {
Element currentElement = (Element)currentNode;
if (currentElement.getTagName().equals("param")) {
this.setParameter(currentElement, propSetter);
} else if (currentElement.getTagName().equals("layout")) {
appender.setLayout(this.parseLayout(currentElement));
} else if (currentElement.getTagName().equals("filter")) {
this.parseFilters(currentElement, appender);
} else if (currentElement.getTagName().equals("errorHandler")) {
this.parseErrorHandler(currentElement, appender);
} else if (currentElement.getTagName().equals("appender-ref")) {
String refName = this.subst(currentElement.getAttribute("ref"));
if (appender instanceof AppenderAttachable) {
AppenderAttachable aa = (AppenderAttachable)appender;
LogLog.debug("Attaching appender named [" + refName + "] to appender named [" + appender.getName() + "].");
aa.addAppender(this.findAppenderByReference(currentElement));
} else {
LogLog.error("Requesting attachment of appender named [" + refName + "] to appender named [" + appender.getName() + "] which does not implement org.apache.log4j.spi.AppenderAttachable.");
}
}
}
}
propSetter.activate();
return appender;
} catch (Exception var13) {
LogLog.error("Could not create an Appender. Reported error follows.", var13);
return null;
}
}
在看一下Appender这个对象,这是一个接口,具体的实现在log4j的配置文件中的appender这个元素中的class里面,比如上方定义的appender 中class为org.apache.log4j.RollingFileAppender,
通过代码关系可以知道RollingFileAppender->FileAppender->WriterAppender->AppenderSkeleton->Appedner (这里为什么要说一下这个继承关系,等下后面会说明)
在parseAppender的执行过程中,会解析配置文件中的layout参数,解析layout参数它使用的如下代码,具体步骤如下:
1.加载org.apache.log4j.PatternLayout这个类
2.然后实例化layout接口
3.通过PropertySetter属性加载器,将ConversionPattern属性加载到layout接口中
4.最后返回layout对象(ps:具体的参数设置过程,可以自行跟踪代码查看)
protected Layout parseLayout(Element layout_element) {
String className = this.subst(layout_element.getAttribute("class"));
LogLog.debug("Parsing layout of class: \"" + className + "\"");
try {
Object instance = Loader.loadClass(className).newInstance();
Layout layout = (Layout)instance;
PropertySetter propSetter = new PropertySetter(layout);
NodeList params = layout_element.getChildNodes();
int length = params.getLength();
for(int loop = 0; loop < length; ++loop) {
Node currentNode = params.item(loop);
if (currentNode.getNodeType() == 1) {
Element currentElement = (Element)currentNode;
String tagName = currentElement.getTagName();
if (tagName.equals("param")) {
this.setParameter(currentElement, propSetter);
}
}
}
propSetter.activate();
return layout;
} catch (Exception var12) {
LogLog.error("Could not create the Layout. Reported error follows.", var12);
return null;
}
}
返回的Layout通过setLayout方法设置到Appender中,这样Appender就设置好了日志格式,
由于Appender是一个接口,setLayout的具体实现在AppenderSkeleton中,上文对RollingFileAppender的继承关系进行了说明,方便可以查看具体的实现
以上log日志的格式就加载完了,那log4j是如何使用它的,在我们要记录log的时候,以info为例
这里在记录的log的时候(具体是怎么执行的,在下一个记录中详细说明,因为涉及到写文件的内容,这篇估计写不完)
执行匹配log记录格式的代码如下,这段代码是在PatternLayout中实现的Layout接口
这个是PatternLayout的源码
public class PatternLayout
extends Layout
{
public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
public static final String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %x - %m%n";
protected final int BUF_SIZE = 256;
protected final int MAX_CAPACITY = 1024;
private StringBuffer sbuf = new StringBuffer(256);
private String pattern;
private PatternConverter head;
public PatternLayout()
{
this("%m%n");
}
public PatternLayout(String pattern)
{
this.pattern = pattern;
this.head = createPatternParser(pattern == null ? "%m%n" : pattern).parse();
}
public void setConversionPattern(String conversionPattern)
{
this.pattern = conversionPattern;
this.head = createPatternParser(conversionPattern).parse();
}
public String getConversionPattern()
{
return this.pattern;
}
public void activateOptions() {}
public boolean ignoresThrowable()
{
return true;
}
protected PatternParser createPatternParser(String pattern)
{
return new PatternParser(pattern);
}
public String format(LoggingEvent event)
{
if (this.sbuf.capacity() > 1024) {
this.sbuf = new StringBuffer(256);
} else {
this.sbuf.setLength(0);
}
PatternConverter c = this.head;
while (c != null)
{
c.format(this.sbuf, event);
c = c.next;
}
return this.sbuf.toString();
}
}
可以看format的执行过程
先初始化了PatternConverter 对象c ,将head对象赋予 c,head在PatternLayout初始化的过程中就进行了创建,创建过程如下
public PatternLayout(String pattern) {
this.BUF_SIZE = 256;
this.MAX_CAPACITY = 1024;
this.sbuf = new StringBuffer(256);
this.pattern = pattern;
this.head = this.createPatternParser(pattern == null ? "%m%n" : pattern).parse();
}
protected PatternParser createPatternParser(String pattern) {
return new PatternParser(pattern);
}
public PatternConverter parse() {
this.i = 0;
while(true) {
while(true) {
while(this.i < this.patternLength) {
char c = this.pattern.charAt(this.i++);
switch(this.state) {
case 0:
if (this.i == this.patternLength) {
this.currentLiteral.append(c);
} else if (c == '%') {
switch(this.pattern.charAt(this.i)) {
case '%':
this.currentLiteral.append(c);
++this.i;
break;
case 'n':
this.currentLiteral.append(Layout.LINE_SEP);
++this.i;
break;
default:
if (this.currentLiteral.length() != 0) {
this.addToList(new PatternParser.LiteralPatternConverter(this.currentLiteral.toString()));
}
this.currentLiteral.setLength(0);
this.currentLiteral.append(c);
this.state = 1;
this.formattingInfo.reset();
}
} else {
this.currentLiteral.append(c);
}
break;
case 1:
this.currentLiteral.append(c);
switch(c) {
case '-':
this.formattingInfo.leftAlign = true;
break;
case '.':
this.state = 3;
break;
default:
if (c >= '0' && c <= '9') {
this.formattingInfo.min = c - 48;
this.state = 4;
} else {
this.finalizeConverter(c);
}
}
case 2:
default:
break;
case 3:
this.currentLiteral.append(c);
if (c >= '0' && c <= '9') {
this.formattingInfo.max = c - 48;
this.state = 5;
break;
}
LogLog.error("Error occured in position " + this.i + ".\n Was expecting digit, instead got char \"" + c + "\".");
this.state = 0;
break;
case 4:
this.currentLiteral.append(c);
if (c >= '0' && c <= '9') {
this.formattingInfo.min = this.formattingInfo.min * 10 + (c - 48);
} else if (c == '.') {
this.state = 3;
} else {
this.finalizeConverter(c);
}
break;
case 5:
this.currentLiteral.append(c);
if (c >= '0' && c <= '9') {
this.formattingInfo.max = this.formattingInfo.max * 10 + (c - 48);
} else {
this.finalizeConverter(c);
this.state = 0;
}
}
}
if (this.currentLiteral.length() != 0) {
this.addToList(new PatternParser.LiteralPatternConverter(this.currentLiteral.toString()));
}
return this.head;
}
}
}
在其中解析格式中每个字母对应的内容的内容为finalizeConverter方法,具体代码如下
protected void finalizeConverter(char c) {
PatternConverter pc = null;
switch(c) {
case 'C':
pc = new PatternParser.ClassNamePatternConverter(this.formattingInfo, this.extractPrecisionOption());
this.currentLiteral.setLength(0);
break;
case 'F':
pc = new PatternParser.LocationPatternConverter(this.formattingInfo, 1004);
this.currentLiteral.setLength(0);
break;
case 'L':
pc = new PatternParser.LocationPatternConverter(this.formattingInfo, 1003);
this.currentLiteral.setLength(0);
break;
case 'M':
pc = new PatternParser.LocationPatternConverter(this.formattingInfo, 1001);
this.currentLiteral.setLength(0);
break;
case 'X':
String xOpt = this.extractOption();
pc = new PatternParser.MDCPatternConverter(this.formattingInfo, xOpt);
this.currentLiteral.setLength(0);
break;
case 'c':
pc = new PatternParser.CategoryPatternConverter(this.formattingInfo, this.extractPrecisionOption());
this.currentLiteral.setLength(0);
break;
case 'd':
String dateFormatStr = "ISO8601";
String dOpt = this.extractOption();
if (dOpt != null) {
dateFormatStr = dOpt;
}
Object df;
if (dateFormatStr.equalsIgnoreCase("ISO8601")) {
df = new ISO8601DateFormat();
} else if (dateFormatStr.equalsIgnoreCase("ABSOLUTE")) {
df = new AbsoluteTimeDateFormat();
} else if (dateFormatStr.equalsIgnoreCase("DATE")) {
df = new DateTimeDateFormat();
} else {
try {
df = new SimpleDateFormat(dateFormatStr);
} catch (IllegalArgumentException var7) {
LogLog.error("Could not instantiate SimpleDateFormat with " + dateFormatStr, var7);
df = (DateFormat)OptionConverter.instantiateByClassName("org.apache.log4j.helpers.ISO8601DateFormat", class$java$text$DateFormat != null ? class$java$text$DateFormat : (class$java$text$DateFormat = class$("java.text.DateFormat")), (Object)null);
}
}
pc = new PatternParser.DatePatternConverter(this.formattingInfo, (DateFormat)df);
this.currentLiteral.setLength(0);
break;
case 'l':
pc = new PatternParser.LocationPatternConverter(this.formattingInfo, 1000);
this.currentLiteral.setLength(0);
break;
case 'm':
pc = new PatternParser.BasicPatternConverter(this.formattingInfo, 2004);
this.currentLiteral.setLength(0);
break;
case 'p':
pc = new PatternParser.BasicPatternConverter(this.formattingInfo, 2002);
this.currentLiteral.setLength(0);
break;
case 'r':
pc = new PatternParser.BasicPatternConverter(this.formattingInfo, 2000);
this.currentLiteral.setLength(0);
break;
case 't':
pc = new PatternParser.BasicPatternConverter(this.formattingInfo, 2001);
this.currentLiteral.setLength(0);
break;
case 'x':
pc = new PatternParser.BasicPatternConverter(this.formattingInfo, 2003);
this.currentLiteral.setLength(0);
break;
default:
LogLog.error("Unexpected char [" + c + "] at position " + this.i + " in conversion patterrn.");
pc = new PatternParser.LiteralPatternConverter(this.currentLiteral.toString());
this.currentLiteral.setLength(0);
}
this.addConverter((PatternConverter)pc);
}
这个部分对格式中每个字母都进行分别处理,不同的字母对应不同的Converter
分别是ClassNamePatternConverter,LocationPatternConverter,MDCPatternConverter,CatgegoryPatternConverter,DatePatternConverter,BasicPatternConverter,这其中还包含ISO80601格式
不同的Converter对应不同的日志内容
例如我配置的消息格式为%m%n
根据代码匹配的是BasicPatternConverter,在PatternLayout format的过程中调用的convert方法实际执行的就是匹配的Converter中的convert方法,可以看出%m 对应的是2004 所以只会打印获取到的Message
private static class BasicPatternConverter extends PatternConverter {
int type;
BasicPatternConverter(FormattingInfo formattingInfo, int type) {
super(formattingInfo);
this.type = type;
}
public String convert(LoggingEvent event) {
switch(this.type) {
case 2000:
return Long.toString(event.timeStamp - LoggingEvent.getStartTime());
case 2001:
return event.getThreadName();
case 2002:
return event.getLevel().toString();
case 2003:
return event.getNDC();
case 2004:
return event.getRenderedMessage();
default:
return null;
}
}
}
至于每个字母前的数字,在PatternConverter中的format方法里根据这个数据去对内容进行了截取
其中min和max的值来源于PatternConverter的parse过程,值保存在formattingInfo里面,如下截图
所以到目前为止,已经对Log4j是如何匹配消息格式的,以及每个消息各种的字母的含义,目前已经大致搞清楚来龙去脉,后续会继续对日志的写入进行源码的解读学习
马上过年了,祝大家新年快乐,各位大大来年在见