• logback.xml日志输出原理分析


    childrenList

    0 = {Logger@13299} “Logger[SYSLOGGER]”
    1 = {Logger@6444}Logger[BASELOGGER]"
    2 = {Logger@13300} “Logger[BUSILOGGER]”
    3 = {Logger@13301} “Logger[MONLOGGER]”
    4 = {Logger@13302} “Logger[org]”
    5 = {Logger@13303} “Logger[com]”
    6 = {Logger@13304} “Logger[cn]”

    appenderList

    2 = {Logger@13300} “Logger[BUSILOGGER]” 下面有个appenderList,appenderList下面有2个输出目的地。

    0 = {AsyncAppender@13142} ch.qos.logback.classic.AsyncAppender[ASYNC_SYSFILE]"
    1 = {ConsoleAppender@10175} “ch.qos.logback.core.ConsoleAppender[STDOUT]”

    这2个目的地就是对应我们的Logback配置文件。一个是控制台,一个是日志文件。

    public int appendLoopOnAppenders(E e) {
        int size = 0;
        final Appender<E>[] appenderArray = appenderList.asTypedArray();
        final int len = appenderArray.length;
        for (int i = 0; i < len; i++) {
            appenderArray[i].doAppend(e);  通过doAppend方法对2个目的地追加要打印的日志。
            size++;
        }
        return size;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在执行subAppend方法的时候,会对日志按照指定的输出logback.xml配置的格式转换成byte

    protected void subAppend(E event) {
        byte[] byteArray = this.encoder.encode(event);
        writeBytes(byteArray);   //滚动写入到对应的目的地。
    }
    
    public byte[] encode(E event) {
        String txt = layout.doLayout(event);
        return convertToBytes(txt);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    layout就是日志格式对象,该对象包含很多属性,日志格式属性对象比如下面这种

    [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%cn] [%X{tid}] [%ip] [%ALogger] [%-5level] [%thread] [%AClass{36}:%ALine] [%X{pid}] [%X{sid}] #%msg#%n
    
    • 1

    最终会调用下面方法,来遍历虽有的处理类,将日志格式中的变量替换,这里就要重点介绍处理类了。

    protected String writeLoopOnConverters(E event) {
        StringBuilder strBuilder = new StringBuilder(INTIAL_STRING_BUILDER_SIZE); 定义字符串,通过append的方式追加到尾部。
        Converter<E> c = head;   //head表示第一个处理类,head有个方法getNext(),就是拿到第二个处理类,每个处理类都有next,直到最后一个next为null
        while (c != null) {
            c.write(strBuilder, event);  //调用【处理类】的write方法,把要替换的日志写到strBuilder里面去。
            c = c.getNext();
        }
        return strBuilder.toString();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    日志格式里面有很多变量,都需要不同的【处理类】来替换,那么每个变量都有对应的处理类,比如上面我们定义了变量tid、ip、ALine、pid这是我们自己定义的变量,那么我们就要往里面添加对应的处理类,比如处理Ip的处理类IpConverter.class,处理ALine变量的类ALineOfCallerConverter.class,这都是处理类,都是自己定义的。下面会介绍如何定义这些处理类。

    定义日志输出格式:

    方式一:

    <encoder> 
    	<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %npattern> 
    encoder> 
    
    • 1
    • 2
    • 3

    方式二:

    <encoder class="cn.com.xxx.component.logback.encoder.PatternLayoutEncoder">encoder>
    
    • 1

    PatternLayoutEncoder是我们自己定义的类,继承了PatternLayoutEncoderBase接口

    public class PatternLayoutEncoder extends PatternLayoutEncoderBase<ILoggingEvent> {
    	//定义输出格式
        private static String PATTERN_SUFFIX = "[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%cn] [%X{tid}] [%ip] [%ALogger] [%-5level] [%thread] [%AClass{36}:%Line] [%X{pid}] [%X{sid}] #%msg#%n";
    
        public PatternLayoutEncoder() {
        }
    
        //重写了父类start方法
        public void start() {
            APatternLayout patternLayout = new APatternLayout();
            patternLayout.setContext(this.context);
            patternLayout.setPattern(PATTERN_SUFFIX);
            patternLayout.setOutputPatternAsHeader(this.outputPatternAsHeader);
            patternLayout.start();
            this.layout = patternLayout;
            super.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    上面有个APatternLayout,主要是完成往【处理类Map】里面添加我们自定义的【处理类】

    public class APatternLayout extends PatternLayout {
        public APatternLayout() {
        }
    
        static {
        	//defaultConverterMap处理类的Map
    
            defaultConverterMap.put("ip", IpConverter.class.getName());
            defaultConverterMap.put("AClass", AClassConverter.class.getName());
            defaultConverterMap.put("ALine", ALineOfCallerConverter.class.getName());
            defaultConverterMap.put("ALogger", ALoggerPatternConverter.class.getName());
            defaultConverterMap.put("X", LogbackMDCPatternConverter.class.getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    比如IpConverter.class处理类,需要继承ClassicConverter类,然后重写对应的convert方法。

    public class IpConverter extends ClassicConverter {
        public IpConverter() {
        }
    
        public String convert(ILoggingEvent event) {
            return getLocalHostIP();
        }
    
        private static String getLocalHostIP() {
            try {
                InetAddress addr = InetAddress.getLocalHost();
                return addr.getHostAddress();
            } catch (UnknownHostException var1) {
                return "127.0.0.1";
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    java之你真正了解抽象类和接口嘛?
    .NET高性能开发-位图索引(一)
    Tracking::TrackLocalMap()
    tf.lite
    MacBookPro 安装cx_Oracle,并配置环境
    自动化测试----selenium(二)
    Idea创建springboot工程的时候,发现pom文件没有带<parent>标签
    Softing为连接PROFIBUS网络提供多种接口产品方案
    麻雀搜索算法(SSA)与支持向量机(SVM)结合的预测模型(SSA-SVM)及其Python和MATLAB实现
    二、使用DockerCompose部署RocketMQ
  • 原文地址:https://blog.csdn.net/weixin_37862824/article/details/127407909