关于Log4J记录日志格式加载以及匹配源码解读

本文详细解释了Log4J如何根据配置文件解析日志格式,重点讲解了PatternLayout的使用和PatternConverter在匹配不同日志信息时的作用,包括%m、%n等符号的含义。
摘要由CSDN通过智能技术生成

之前看了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是如何匹配消息格式的,以及每个消息各种的字母的含义,目前已经大致搞清楚来龙去脉,后续会继续对日志的写入进行源码的解读学习

马上过年了,祝大家新年快乐,各位大大来年在见

  • 35
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值