你用了这么久的Log4j2日志框架,真的对它有自己的理解吗?

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 你用了这么久的Log4j2日志框架,真的对它有自己的理解吗?

点击上方“Java知音”,选择“置顶公众号”

技术文章第一时间送达!

作者:蜗牛大师 cnblogs.com/wuqinglong/p/9516529.html

cnblogs.com/wuqinglong/p/9516529.html

1.日志框架

日志接口(slf4j)

slf4j是对所有日志框架制定的一种规范、标准、接口,并不是一个框架的具体的实现,因为接口并不能独立使用,需要和具体的日志框架实现配合使用(如log4j、logback)

日志实现(log4j、logback、log4j2)

  • log4j是apache实现的一个开源日志组件
  • logback同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架,是slf4j的原生实现
  • log4j2是log4j 1.x和logback的改进版,据说采用了一些新技术(无锁异步、等等),使得日志的吞吐量、性能比log4j 1.x提高10倍,并解决了一些死锁的bug,而且配置更加简单灵活。
  • logback同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架,是slf4j的原生实现

    2.为什么需要日志接口,直接使用具体的实现不就行了吗?

    接口用于定制规范,可以有多个实现,使用时是面向接口的(导入的包都是slf4j的包而不是具体某个日志框架中的包),即直接和接口交互,不直接使用实现,所以可以任意的更换实现而不用更改代码中的日志相关代码。

    比如:slf4j定义了一套日志接口,项目中使用的日志框架是logback,开发中调用的所有接口都是slf4j的,不直接使用logback,调用是 自己的工程调用slf4j的接口,slf4j的接口去调用logback的实现,可以看到整个过程应用程序并没有直接使用logback,当项目需要更换更加优秀的日志框架时(如log4j2)只需要引入Log4j2的jar和Log4j2对应的配置文件即可,完全不用更改Java代码中的日志相关的代码logger.info(“xxx”),也不用修改日志相关的类的导入的包(import org.slf4j.Logger; import org.slf4j.LoggerFactory;)

    使用日志接口便于更换为其他日志框架

    log4j、logback、log4j2都是一种日志具体实现框架,所以既可以单独使用也可以结合slf4j一起搭配使用。

    本文使用Log4j2作为slf4j的具体实现,引入的包如下:

    
    dependency
        groupIdorg.slf4j/groupId
        artifactIdslf4j-api/artifactId
        version1.7.25/version
    /dependency
    dependency
        groupIdorg.apache.logging.log4j/groupId
        artifactIdlog4j-slf4j-impl/artifactId
        version2.11.0/version
    /dependency
    dependency
        groupIdorg.apache.logging.log4j/groupId
        artifactIdlog4j-core/artifactId
        version2.11.0/version
    /dependency
    dependency
        groupIdorg.apache.logging.log4j/groupId
        artifactIdlog4j-api/artifactId
        version2.11.0/version
    /dependency
    

    3.log4j2日志级别

    从大到小依次是: off, fatal, error, warn, info, debug, trace, all

    由于我们使用的是slf4j接口包,该接口包中只提供了未标有删除线的日志级别的输出。

    4.log4j2配置文件的优先级

    • Log4j will inspect the log4j.configurationFile system property and, if set, will attempt to load the configuration using the ConfigurationFactory that matches the file extension.
    • If no system property is set the properties ConfigurationFactory will look for log4j2-test.properties in the classpath.
    • If no such file is found the YAML ConfigurationFactory will look for log4j2-test.yaml or log4j2-test.yml in the classpath.
    • If no such file is found the JSON ConfigurationFactory will look for log4j2-test.json or log4j2-test.jsn in the classpath.
    • If no such file is found the XML ConfigurationFactory will look for log4j2-test.xml in the classpath.
    • If a test file cannot be located the properties ConfigurationFactory will look for log4j2.properties on the classpath.
    • If a properties file cannot be located the YAML ConfigurationFactory will look for log4j2.yaml or log4j2.yml on the classpath.
    • If a YAML file cannot be located the JSON ConfigurationFactory will look for log4j2.json or log4j2.jsn on the classpath.
    • If a JSON file cannot be located the XML ConfigurationFactory will try to locate log4j2.xml on the classpath.
    • If no configuration file could be located the DefaultConfiguration will be used. This will cause logging output to go to the console.

    5.对于log4j2配置文件的理解

    配置文件结构:

  • `Appdenders`部分
    1. `Appender`
      1. `Filter`
      2. `Layout`
      3. `Policies`
      4. `Strategy`
      5. `Appender`
      6. Loggers部分

      7. `Logger`
      8. `RootLogger`
      9. 6.对于Appender的理解

        简单说Appender就是一个管道,定义了日志内容的去向(保存位置)。

        配置一个或者多个Filter,Filter的过滤机制和Servlet的Filter有些差别,下文会进行说明。

      10. 配置Layout来控制日志信息的输出格式。
      11. 配置Policies以控制日志何时(When)进行滚动。
      12. 配置Strategy以控制日志如何(How)进行滚动。
      13. 配置Policies以控制日志何时(When)进行滚动。

        7.对于Logger的理解

        简单说Logger就是一个路由器,指定类、包中的日志信息流向哪个管道,以及控制他们的流量(日志级别)

        8.log4j2配置文件框架

        配置文件格式

        
        ?xml version="1.0" encoding="UTF-8"?
        
        Configuration
        
            Appenders
        
                Appender
                    Filters
                        LevelRangeFilter minLevel="..." maxLevel="..." onMatch="..." onMismatch="..."/
                    /Filters
        
                    PatternLayout pattern="..." charset="..."/
        
                    Policies
                        CronTriggeringPolicy schedule="..."/
                        SizeBasedTriggeringPolicy size="..."/
                        TimeBasedTriggeringPolicy /
                    /Policies
                /Appender
        
                Appender
                    // ...
                /Appender
        
            /Appenders
        
            Loggers
        
                Logger
                    AppenderRef ref="..."
                /Logger
        
                Root
                    AppenderRef ref="..."
                /Root
        
            /Loggers
        
        /Configuration
        

        9.Appender标签的实现类

        其实这些标签都是类名或者类名去掉后缀。

        Appender的常用的实现类有:

      14. ConsoleAppender(Console)
      15. FileAppender(File)、RandomAccessFileAppender(RandomAccessFile)
      16. RollingFileAppender(RollingFile)、RollingRandomAccessFileAppender(RollingRandomAccessFile)
      17. FileAppender(File)、RandomAccessFileAppender(RandomAccessFile)

        打开这些实现类的源码,你一定会恍然大明白,括号中的是实现类在log4j2.xml配置文件中的标签名。

        10.ConsoleAppender(Console)

        该实现类会把日志输出到控制台中。

        它有两种输出方式:

      18. SYSTEM_OUT(System.out)
      19. SYSTEM_ERR(System.err)
      20. SYSTEM_ERR(System.err)

        如果不配置,默认使用SYSTEM_OUT进行输出。括号中是调用的方法。

        简单示例:

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                Console name="Console" target="SYSTEM_OUT"
                    !-- 格式化日志 --
                    PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/
                /Console
        
            /Appenders
        
            Loggers
        
                !-- level默认为error --
                Root level="info"
                    !-- 这里引用了Appenders标签中的name值 --
                    AppenderRef ref="RollingFile"/
                /Root
        
            /Loggers
        
        /Configuration
        

        其它属性可以参见官方文档: 

        http://logging.apache.org/log4j/2.x/manual/appenders.html#ConsoleAppender

        10-1.FileAppender(File)、RandomAccessFileAppender(RandomAccessFile)

        相同点:写入日志信息到文件

        不同点:使用的I/O实现类不同,前者使用FileOutputStream,后者使用RandomAccessFile。

        官方文档说是在bufferedIO=true(默认是true)的情况下后者比前者性能提升20% ~ 200%,不明觉厉,就用后者吧。

        简单示例:

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                RandomAccessFile name="File" fileName="logs/app.log" immediateFlush="false"
                    PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/
                /RandomAccessFile
        
            /Appenders
        
            Loggers
        
                Root level="info"
                    AppenderRef ref="File"/
                /Root
        
            /Loggers
        
        /Configuration
        

        常用属性:

      21. **fileName:**来指定文件位置,文件或目录不存在则会自动创建。
      22. **immediateFlush:**是否每次写入都要立刻刷新到硬盘中。默认true,如果使用默认值可能会影响性能。
      23. immediateFlush:是否每次写入都要立刻刷新到硬盘中。默认true,如果使用默认值可能会影响性能。

        其它属性可以参见官方文档:

        http://logging.apache.org/log4j/2.x/manual/appenders.html#RandomAccessFileAppender

        10-2.RollingFileAppender(RollingFile)、RollingRandomAccessFileAppender(RollingRandomAccessFile)

        这一对之间的区别与上一对之间的区别是一样的。

        上一对的实现类不能进行日志滚动,所谓日志滚动就是当达到设定的条件后,日志文件进行切分。

        比如:工程师想让系统中的日志按日进行切分,并且按月归档。

        这时候这一对的作用就体现出来了。

        简单示例:

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                RollingRandomAccessFile name="File" fileName="logs/app.log"
                                         filePattern="logs/$${date:hh-mm}/%d{hh-mm-ss}.app.%i.log" 
                    PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/
        
                    Policies
                        !-- 每 5s 翻滚一次 --
                        CronTriggeringPolicy schedule="0/5 * * * * ?" /
                        SizeBasedTriggeringPolicy size="10 MB"/
                    /Policies
        
                    DefaultRolloverStrategy max="10" /
        
                /RollingRandomAccessFile
        
            /Appenders
        
            Loggers
        
                Root level="info"
                    AppenderRef ref="File"/
                /Root
        
            /Loggers
        
        /Configuration
        

        1.filePattern:指定了日志滚动之后的文件命名格式,至于其中的{date:hh-mm}表达式下文介绍。

        2.DefaultRolloverStrategy:指定了如何(How)进行翻滚,并且指定了最大翻滚次数(影响%i参数值),超过次数之后会按照相应的规则删除旧日志。

        3.Policies: 这里就是规定了何时进行滚动(When),可以有多个Policy。

      24. CronTriggeringPolicy设置了每 5s 进行一次翻滚
      25. SizeBasedTriggeringPolicy设置了的话,如果当前文件超过了10MB,但是文件的名字还没有进行翻滚(建立新文件),那么就会用%i的方式进行翻滚。
      26. SizeBasedTriggeringPolicy设置了的话,如果当前文件超过了10MB,但是文件的名字还没有进行翻滚(建立新文件),那么就会用%i的方式进行翻滚。

        10-3.翻滚示例

        app.log

        第一次翻滚:app.log app.1.log // app.log - app.1.log
        第二次翻滚:app.log app.1.log app.2.lop // app.log - app.2.log
        第三次翻滚:app.log app.1.log app.2.lop app.3.lop // app.log - app.3.log
        第四次翻滚:app.log app.1.log app.2.lop app.3.lop app.4.lop // app.log - app.4.log

        一直到设定的翻滚次数10之后,会把旧的日志内容覆盖。

        
        app.2.lop - app.1.lop
        app.3.lop - app.2.lop
        ...
        app.10.lop - app.9.lop
        app.log - app.10.lop
        

        一直这样循环下去,直到创建新文件。

        11.Filters

        Filters决定日志事件能否被输出。过滤条件有三个值:ACCEPT(接受),DENY(拒绝),NEUTRAL(中立)。

        11-1.常用的Filter实现类有

      27. LevelRangeFilter
      28. TimeFilter
      29. ThresholdFilter
      30. TimeFilter

        11-2.上文中提到log4j2中的Filter与Servlet中的有差别。那么有什么差别呢?

        简单说就是log4j2中的过滤器ACCEPT和DENY之后,后续的过滤器就不会执行了,只有在NEUTRAL的时候才会执行后续的过滤器。

        11-3.简单示例

        测试代码:

        
        public class LogMain {
        
            private static Logger logger = LoggerFactory.getLogger(LogMain.class);
        
            public static void main(String[] args) throws Exception {
        
                logger.trace("trace Msg.");
                logger.debug("debug Msg.");
                logger.info("info Msg.");
                logger.warn("warn Msg.");
                logger.error("error Msg.");
        
            }
        
        }
        

        配置文件:

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                Console name="Console"
        
                    !--
                        设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器
                        最后一个过滤器建议设置 onMismatch="DENY", 不然日志就输出了。
                    --
                    Filters
        
                        !-- 从大到小:error, warn, info, debug, trace --
                        LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="NEUTRAL" /
        
                        !-- 只允许在每天的 8~8点半 之间输出日志 --
                        TimeFilter start="08:00:00" end="08:30:00" onMatch="ACCEPT" onMismatch="DENY" /
                    /Filters
        
                    PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/
                /Console
        
            /Appenders
        
            Loggers
        
                Root level="info"
                    AppenderRef ref="Console"/
                /Root
        
            /Loggers
        
        /Configuration
        

        输出结果:

        
        17:51:53.546 [main] INFO  me.master.snail.log.LogMain - info Msg.
        17:51:53.548 [main] WARN  me.master.snail.log.LogMain - warn Msg.
        17:51:53.548 [main] ERROR me.master.snail.log.LogMain - error Msg.
        

        如果当前时间不是 8点~8点半 之间,那么没有日志会输出。

        这里的info Msg.、warn Msg.和error Msg.为什么会输出呢?

        是因为LevelRangeFilter对它们进行了ACCEPT,而剩下的trace Msg.和debug Msg.则会经过下一个过滤器,然后依次类推。

        12.PatternLayout

        这是常用的日志格式化类,其它日志格式化类很少用。

        关于其它日志类,可以打开PatternLayout类,找到其父类AbstractStringLayout, 看父类的实现类有哪些。

        简单示例:

        
        PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/
        

        授人以鱼不如授人以渔。关于pattern的格式点击

        http://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout

        具体的其它属性可以看源码也可以参考官方文档。

        13.Policy & Strategy

        上文也说了,Policy是用来控制日志文件何时(When)进行滚动的;Strategy是用来控制日志文件如何(How)进行滚动的。

        如果配置的是RollingFile或RollingRandomAccessFile,则必须配置一个Policy。

        如果想按月归档,按日切分日志,然后

        13-1.Policy常用的实现类:

      31. SizeBasedTriggeringPolicy
      32. CronTriggeringPolicy
      33. TimeBasedTriggeringPolicy
      34. CronTriggeringPolicy

        13-1-1.SizeBasedTriggeringPolicy

        根据日志文件的大小进行滚动。

        
        SizeBasedTriggeringPolicy size="10MB"/
        

        单位有:KB,MB,GB

        13-1-2.CronTriggeringPolicy

        使用Cron表达式进行日志滚动,很灵活。

        
        CronTriggeringPolicy schedule="0/5 * * * * ?" /
        

        13-1-3.TimeBasedTriggeringPolicy

        这个滚动策略依赖于filePattern中配置的最具体的时间单位,根据最具体的时间单位进行滚动。

        这种方式比较简洁。CronTriggeringPolicy策略更强大。

        简单示例:

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                RollingRandomAccessFile name="File" fileName="logs/app.log"
                                         filePattern="logs/$${date:hh-mm}/%d{hh-mm-ss}.app.%i.log" 
                    PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/
        
                    Policies
                        !-- 每 5s 翻滚一次 --
                        !--CronTriggeringPolicy schedule="0/5 * * * * ?" /--
        
                        !--
                            filePattern中最具体的时间单位是 秒。
                            这里用 TimeBasedTriggeringPolicy 替换 CronTriggeringPolicy
        
                            注意:modulate属性是指从启动时间开始算5秒,还是从0秒开始算5秒,运行一下就明白了。
                            modulate: true(默认值) // 会从启动时间开始算 5秒
                            modulate: false // 从 0秒开始算
                        --
                        TimeBasedTriggeringPolicy interval="5" modulate="true"/
        
                        SizeBasedTriggeringPolicy size="10 MB"/
                    /Policies
        
                    DefaultRolloverStrategy max="10" /
        
                /RollingRandomAccessFile
        
            /Appenders
        
            Loggers
        
                Root level="info"
                    AppenderRef ref="File"/
                /Root
        
            /Loggers
        
        /Configuration
        

        13-2.Strategy常用的实现类

      35. DefaultRolloverStrategy
      36. DirectWriteRolloverStrategy
      37. DirectWriteRolloverStrategy

        这两个Strategy都是控制如何进行日志滚动的,至于他们的区别我还是不太明白,大佬解释一下吧。

        平时大部分用DefaultRolloverStrategy就可以了。

        14.Logger

        Logger部分就比较简单了,分为两个Logger:

      38. Root(必须配置)
      39. Logger
      40. Logger

        简单示例:

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                Console name="Console"
                    PatternLayout
                        Pattern%d %p %c{1.} [%t] %m%n/Pattern
                    /PatternLayout
                /Console
        
            /Appenders
        
            Loggers
        
                Root level="trace"
                    AppenderRef ref="Console"/
                    Filters
                        LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="DENY" /
                    /Filters
                /Root
        
            /Loggers
        
        /Configuration
        

        注意:Logger中也可以加过滤器的哟~

        14-1.比较重要的问题: 日志重复打印

        如果Root中的日志包含了Logger中的日志信息,并且AppenderRef是一样的配置,则日志会打印两次。

        注意:有两个条件

      41. Root中的日志包含了Logger中的日志信息
      42. 且AppenderRef是一样的配置
      43. 且AppenderRef是一样的配置

        这时候我们需要使用一个Logger的属性来解决,那就是additivity,其默认值为true,需要配置为false。

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                Console name="Console"
                    PatternLayout
                        Pattern%d %p %c{1.} [%t] %m%n/Pattern
                    /PatternLayout
                /Console
        
            /Appenders
        
            Loggers
        
                Logger name="me.master.snail.log.LogMain" level="info" additivity="false"
                    AppenderRef ref="Console"/
                /Logger
        
                Root level="trace"
                    AppenderRef ref="Console"/
                    Filters
                        LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="DENY" /
                    /Filters
                /Root
        
            /Loggers
        
        /Configuration
        

        15.Lookups

        这个组件类似于JSTL的EL表达式,或者类似于Spring的SpEL表达式。

        具体的语法很简单,这里就不粘贴复制了,查看官方文档:

        http://logging.apache.org/log4j/2.x/manual/lookups.html

        相信你用半个小时就学会了。

        16.示例

        为了大家快速开发(方便懒惰的同学),写一些示例。

        16-1.输出到控制台

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                Console name="Console" target="SYSTEM_OUT"
                    !-- 格式化日志 --
                    PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/
                /Console
        
            /Appenders
        
            Loggers
        
                !-- level默认为error --
                Root level="info"
                    !-- 这里引用了Appenders标签中的name值 --
                    AppenderRef ref="RollingFile"/
                /Root
        
            /Loggers
        
        /Configuration
        

        16-2.输出到单个文件

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                RandomAccessFile name="File" fileName="logs/app.log" immediateFlush="false"
                    PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/
                /RandomAccessFile
        
            /Appenders
        
            Loggers
        
                Root level="info"
                    AppenderRef ref="File"/
                /Root
        
            /Loggers
        
        /Configuration
        

        16-3.按月归档日志,按日进行切分,限制单文件大小为 500MB, 一天最多生成20个文件,也就是(20 * 500)MB大小的日志

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                RollingRandomAccessFile name="File" fileName="logs/app.log"
                                         filePattern="logs/$${date:yyyy-MM}/%d{yyyy-MM-dd}.app.%i.log" 
                    PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/
        
                    Policies
                        TimeBasedTriggeringPolicy interval="1" modulate="false"/
                        SizeBasedTriggeringPolicy size="500MB"/
                    /Policies
        
                    DefaultRolloverStrategy max="20" /
                /RollingRandomAccessFile
        
            /Appenders
        
            Loggers
        
                Root level="info"
                    AppenderRef ref="File"/
                /Root
        
            /Loggers
        
        /Configuration
        

        16-4.限制Spring框架日志的输出级别

        
        ?xml version="1.0" encoding="UTF-8"?
        Configuration name="baseConf" status="warn" monitorInterval="30"
        
            Appenders
        
                RollingRandomAccessFile name="File" fileName="logs/app.log"
                                         filePattern="logs/$${date:yyyy-MM}/%d{yyyy-MM-dd}.app.%i.log" 
                    PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/
        
                    Policies
                        TimeBasedTriggeringPolicy interval="1" modulate="false"/
                        SizeBasedTriggeringPolicy size="500MB"/
                    /Policies
        
                    DefaultRolloverStrategy max="20" /
                /RollingRandomAccessFile
        
            /Appenders
        
            Loggers
        
                !--
                    限制Spring框架日志的输出级别,其它框架类似配置
                    或者使用 AppenderRef 标签,将其输出到指定文件中,记得加上 additivity="false"
                --
                logger name="org.springframework" level="INFO"/
        
                Root level="info"
                    AppenderRef ref="File"/
                /Root
        
            /Loggers
        
        /Configuration
        

        学会以上这些理解和方法,相信就可以熟练使用了,加油!

        END

        Java面试题专栏

        你用了这么久的Log4j2日志框架,真的对它有自己的理解吗?

        我知道你 “在看你用了这么久的Log4j2日志框架,真的对它有自己的理解吗?

        原文始发于微信公众号(Java知音):你用了这么久的Log4j2日志框架,真的对它有自己的理解吗?

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 你用了这么久的Log4j2日志框架,真的对它有自己的理解吗?


     上一篇
    这一篇让你彻底搞懂 JAVA 内部类 这一篇让你彻底搞懂 JAVA 内部类
    点击上方“Java知音”,选择“置顶公众号” 技术文章第一时间送达! 来源:像一只狗 juejin.im/post/5a903ef96fb9a063435ef0c8 juejin.im/post/5a90
    下一篇 
    带你深挖Java泛型类型擦除以及类型擦除带来的问题 带你深挖Java泛型类型擦除以及类型擦除带来的问题
    点击上方“Java知音”,选择“置顶公众号” 技术文章第一时间送达! 作者:蜗牛大师 cnblogs.com/wuqinglong/p/9456193.html cnblogs.com/wuqinglong