• 二、快速开始


    一、从一个小例子开始

    
    package com.example.demo;
    
    import ch.qos.logback.classic.Level;
    import ch.qos.logback.classic.LoggerContext;
    import ch.qos.logback.classic.PatternLayout;
    import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
    import ch.qos.logback.classic.spi.ILoggingEvent;
    import ch.qos.logback.classic.spi.LoggingEvent;
    import ch.qos.logback.core.ConsoleAppender;
    import ch.qos.logback.core.Context;
    import ch.qos.logback.core.Layout;
    import ch.qos.logback.core.pattern.Converter;
    import ch.qos.logback.core.pattern.ConverterUtil;
    import ch.qos.logback.core.pattern.parser.Node;
    import ch.qos.logback.core.pattern.parser.Parser;
    import java.util.HashMap;
    import java.util.Map;
    import org.slf4j.ILoggerFactory;
    import org.slf4j.impl.StaticLoggerBinder;
    
    /**
     * @author zhen_hong
     * @date 2022/8/9 11:21
     */
    public class LogbackDemo1Test {
    
        public static void main(String[] args) throws Exception {
            // 绑定日志实现,这里使用的是logback实现并解析配置,如果存在logback.xml,logback.groovy and so on 或者系统属性中有指定对应的路径
            ILoggerFactory loggerContext = StaticLoggerBinder.getSingleton().getLoggerFactory();
    
            /**
             * String fqcn, Logger logger, Level level, String message,
             *             Throwable throwable, Object[] argArray
             */
            ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger)loggerContext.getLogger(LogbackTest.class.getName());
            // 清除掉绑定时设置的appenders,因为在绑定日志实现的时候,会自动配置
            // 即使没有找到配置文件也会默认给ROOT logger 添加 ConsoleAppender
            logger.detachAndStopAllAppenders();
    
            Context context = (Context)loggerContext;
    
            // 咱们自己创建一个 ConsoleAppender
            ConsoleAppender consoleAppender = new ConsoleAppender();
            consoleAppender.setContext(context);
            // 日志输出编码器,比如设置字符集等操作,通过layout解析出来的日志字符串做进一步的处理
            PatternLayoutEncoder patternLayoutEncoder = new PatternLayoutEncoder() {
                @Override
                public void setLayout(Layout<ILoggingEvent> layout) {
                    this.layout = layout;
                }
            };
            patternLayoutEncoder.setContext(context);
    
            // pattern 解析器,将 pattern 构建成用户能够读的日志
            PatternLayout patternLayout = new PatternLayout();
            patternLayout.setContext(context);
            // 设置格式化 pattern
            patternLayout.setPattern("%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n");
            patternLayout.start();
            
            patternLayoutEncoder.setLayout(patternLayout);
            patternLayoutEncoder.start();
            consoleAppender.setEncoder(patternLayoutEncoder);
            consoleAppender.setName("console");
            consoleAppender.start();
    
            logger.addAppender(consoleAppender);
    
            logger.info("这个是一个测试方法");
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    从上面的一个简单的例子我们看出,要想实现日志打印需要拥有这么几个组件

    • LoggerContext:即日志上下文,可以用来保存一些日志配置过程中的额外属性和获取Logger对象
    • Appender:输出日志,输出到哪,可以自己实现
    • Encoder:在输出日志之前对日志进行编码
    • Layout:在输出日志之前对日志进行一些格式化操作
    • Logger:直接面向用户打印日志的门面接口

    获取 Logger 对象的方法很简单,就是通过 loggerContext.getLogger 获取即可,如果说你传入的loggerName在loggerContext不存在
    那么将会自动创建一个,并且每一级的父logger都是上一级,最顶级是ROOT,代码如下:

    public final Logger ch.qos.logback.classic.LoggerContext.getLogger(java.lang.String)(final String name) {
    
            if (name == null) {
                throw new IllegalArgumentException("name argument cannot be null");
            }
    
            // if we are asking for the root logger, then let us return it without
            // wasting time
            if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
                return root;
            }
    
            int i = 0;
            Logger logger = root;
    
            // check if the desired logger exists, if it does, return it
            // without further ado.
            Logger childLogger = (Logger) loggerCache.get(name);
            // if we have the child, then let us return it without wasting time
            if (childLogger != null) {
                return childLogger;
            }
    
            // if the desired logger does not exist, them create all the loggers
            // in between as well (if they don't already exist)
            String childName;
            while (true) {
                // 按照点号分割
                int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
                if (h == -1) {
                    childName = name;
                } else {
                    childName = name.substring(0, h);
                }
                // move i left of the last point
                i = h + 1;
                synchronized (logger) {
                    childLogger = logger.getChildByName(childName);
                    if (childLogger == null) {
                        childLogger = logger.createChildByName(childName);
                        loggerCache.put(childName, childLogger);
                        incSize();
                    }
                }
                logger = childLogger;
                if (h == -1) {
                    return childLogger;
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    如果我们传入的loggerName是 com.demo.Test, 那么它的父子结构会是这样的

                    ROOT
                      |
                    com
                     |
                  com.demo
                     |
                  com.demo.test
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    看了以上例子之后,你会发现如果我们要定义其他的Appender或者增加logger,我们只需要创建一个logger,然后给这个logger设置
    自己的Appender就可以轻松驾驭实现自己的需求,但是如果直接在代码里写死的话就失去了灵活性,那么我们可不可以通过配置的方式
    来定义Appender呢?当然可以,代码如下:

    • logback-custom.xml配置文件
    
    <configuration debug="false" scan="true" scanPeriod="1 seconds">
    
      <contextName>logbackcontextName>
    
      <if condition='property("env").contains("dev")'>
        <then>
          <property scope="context" name="log.level" value="DEBUG" />
        then>
        <else>
          <property scope="context" name="log.level" value="INFO" />
        else>
      if>
    
      <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n
          pattern>
        encoder>
      appender>
    
      <root level="${log.level}">
        <appender-ref ref="console" />
      root>
    
      
    
    configuration>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 一个简单的java程序例子
    package com.example.demo;
    
    import ch.qos.logback.classic.Logger;
    import ch.qos.logback.classic.LoggerContext;
    import ch.qos.logback.classic.joran.JoranConfigurator;
    import java.net.URL;
    import org.slf4j.ILoggerFactory;
    import org.slf4j.LoggerFactory;
    import org.slf4j.Marker;
    import org.slf4j.MarkerFactory;
    import org.slf4j.impl.StaticLoggerBinder;
    import org.springframework.core.io.ClassPathResource;
    
    /**
     * @author zhen_hong
     * @date 2022/8/9 11:21
     */
    public class LogbackDemo2Test {
    
        public static void main(String[] args) throws Exception {
            // 绑定日志实现,这里使用的是logback实现并解析配置,如果存在logback.xml,logback.groovy and so on 或者系统属性中有指定对应的路径
    //        ILoggerFactory loggerContext = StaticLoggerBinder.getSingleton().getLoggerFactory();
    
            ILoggerFactory loggerContext = LoggerFactory.getILoggerFactory();
            
            // 获取根logger
            ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger)loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
            // 清除默认的基本 appender 配置
            rootLogger.detachAndStopAllAppenders();
    
            // 解析配置文件
            JoranConfigurator configurator = new JoranConfigurator();
            configurator.setContext((LoggerContext)loggerContext);
            URL url = Thread.currentThread().getContextClassLoader().getResource("logback-custom.xml");
            configurator.doConfigure(url);
    
            org.slf4j.Logger logger = loggerContext.getLogger(LogbackParseTest.class.getName());
    
            logger.info("这个是一个测试方法");
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    看到上面的代码,使用了 JoranConfigurator 对 xml 配置文件进行了解析,将解析出来的 Appender,Encoder,Layout,Logger 设置
    到了LoggerContext 中,那么它具体是怎么解析的呢?这个问题留到下一章分析,下面我先来看看另外一个例子:

    package com.example.demo;
    
    import ch.qos.logback.classic.Level;
    import ch.qos.logback.classic.Logger;
    import ch.qos.logback.classic.LoggerContext;
    import ch.qos.logback.classic.PatternLayout;
    import ch.qos.logback.classic.spi.ILoggingEvent;
    import ch.qos.logback.classic.spi.LoggingEvent;
    import ch.qos.logback.core.pattern.Converter;
    import ch.qos.logback.core.pattern.ConverterUtil;
    import ch.qos.logback.core.pattern.parser.Node;
    import ch.qos.logback.core.pattern.parser.Parser;
    import java.util.HashMap;
    import java.util.Map;
    import org.slf4j.ILoggerFactory;
    import org.slf4j.impl.StaticLoggerBinder;
    
    /**
     * @author zhen_hong
     * @date 2022/8/9 11:21
     */
    public class LogbackDemo3Test {
    
        public static void main(String[] args) throws Exception {
            // 绑定日志实现,这里使用的是logback实现并解析配置,如果存在logback.xml,logback.groovy and so on 或者系统属性中有指定对应的路径
            ILoggerFactory loggerContext = StaticLoggerBinder.getSingleton().getLoggerFactory();
    
            // 使用logback解析器解析pattern
            Parser<ILoggingEvent> p = new Parser<>("%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n");
            p.setContext((LoggerContext)loggerContext);
            Node t = p.parse();
            // 转换器,根据pattern中的%d,%logger创建
            Map<String, String> converterMap = new HashMap<>();
            converterMap.putAll(PatternLayout.DEFAULT_CONVERTER_MAP);
    
            // 编译转换器,用于处理形如 %d{xxx} 的占位符
            Converter c = p.compile(t, converterMap);
    
            StringBuilder buf = new StringBuilder();
    
            ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger)loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
    
            // 构建一个日志事件
            ILoggingEvent event = new LoggingEvent(LogbackTest.class.getName(), logger, Level.INFO
                , "这个是一个测试方法", null, null);
            ((LoggingEvent) event).setTimeStamp(System.currentTimeMillis());
    
            ConverterUtil.startConverters(c);
            while (c != null) {
                c.write(buf, event);
                c = c.getNext();
            }
            System.out.println(buf.toString());
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    上面这段代码相当于将 Appender,Encoder,Layout 的基本实现逻辑给暴露出来了,Layout将日志格式pattern进行解析,最后编译
    成Converter,比如 %d{HH:mm:ss.SSS},对应的Converter就是DateConverter,百分号后边的这个d就是用来指定Converter的指令,
    PatternLayout.DEFAULT_CONVERTER_MAP 这个是logback默认实现的一些指令与Converter的对应关系,比如 d -》ch.qos.logback.classic.pattern.DateConverter

    二、总结

    这一章主要是从一个小例子开始去了解我要打印一个日志需要哪些个组件,当了解了需要什么组件之后,那么我们就可以知道哪些组件是
    可以定制的,从而引出通过配置化的方式去定制组件。额外的,在 LogbackDemo3Test.java 这个例子我们直接深入Appender基本上实现
    打印出了日志,让我们对后边分析Appender源码有一个大致的印象,从而更快速的去理解它的运作方式。

  • 相关阅读:
    如何防止图片抖动
    全局Ceph节点宕机处理
    基JavaSwing开发公司管理系统+报告 课程设计 大作业
    SD6.24集训总结
    前端实现克里金插值分析(一)
    如何选择合适的工业相机
    数据结构之七大排序
    QT(9.4)tcp通信,数据库,opencv,
    Games101笔记-线性代数回顾
    npm install 使用了不同版本的node,jenkins打包vue项目,cnpm打包超时,node-sass安装失败
  • 原文地址:https://blog.csdn.net/qq_27785239/article/details/126281772