• 带你掌握Java各种日志框架


     


    一:日志基本概念及框架

    1:什么是日志

      Java程序员在开发项目时都是依赖Eclipse/IDEA等集成开发工具的Debug调试功能来跟踪解决Bug,但项目打包部署发布到了测试环境和生产环境怎么办?难道连接到生产服务器装个IDEA做远程调试,实际并不能允许让你这么做。所以,日志的作用就是在测试环境和生产环境没有Debug调试工具时为开发人员和测试人员定位问题的手段日志打得好,就能根据日志的轨迹快速定位并解决线上问题,反之,日志输出不好,不仅无法辅助定位问题反而可能会影响到程序的运行性能和稳定性。

      很多介绍 AOP 的地方都采用日志来作为介绍,实际上日志要采用切面的话是极其不科学的!对于日志来说,只是在方法开始、结束、异常时输出一些日志信息,那是绝对不够的,这样的日志对于日志分析没有任何意义。如果在方法的开始和结束记录个日志,那方法中呢?如果方法中没有日志的话,那就完全失去了日志的意义!如果应用出现问题要查找由什么原因造成的,也没有什么作用。这样的日志还不如不用!

    2:日志的作用

    复制代码
    不管是使用何种编程语言,日志输出几乎无处不再。总结起来,日志大致有以下几种用途:
    「问题追踪」:辅助排查和定位线上问题,优化程序运行性能。
    「状态监控」:通过日志分析,可以监控系统的运行状态。
    「安全审计」:审计主要体现在安全上,可以发现非授权的操作。
    日志在应用程序中是非常重要的,好的日志信息能有助于我们在程序出现BUG时能快速进行定位,并能找出其中的原因。
    复制代码

    3:常用日志框架

      按照出来的时间顺序排列:Log4j(reload4j) --> JUL --> JCL --> SLF4J --> Logback --> Log4j2

    二:JUL日志框架

      JUL全称Java Util Logging,是Java原生的日志框架,使用时不需要另外引入第三方类库,相对于其他日志框架来说其特点是使用方便,能够在小型应用中灵活应用。但JUL日志框架使用的频率并不高,但一旦需要解除此类的代码,仍然要求开发人员能够迅速看懂代码,并理解。

    1:框架结构图

    复制代码
    Loggers:
        被称为记录器,应用程序通过获取Logger对象,调用其API来发布日志信息,Logger通常是应用程序访问日志系统的入口程序;
    Appenders:
        也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联的Handlers处理,由Handlers负责将日志记录;
        Handlers在此是一个抽象类,由其具体的实现决定日志记录的位置是控制台、文件、网络上的其他日志服务异或是操作系统日志;
    Layouts:
        也被称为Formatters,它负责对日志事件中的数据进行转换和格式化,Layouts决定了记录的数据在一条日志记录中的最终显示形式;
    Level:
        每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,可以将Level和Loggers或Appenders做关联以便于我们
      过滤消息; Filters: 过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。 一个完整的日志记录过程如下: 用户使用Logger来进行日志记录的行为,Logger可以同时持有若干个Handler,日志输出操作是由Handler完成的;在Handler输出日志前,
      会经过Filter的过滤,判断哪些日志级别放行、哪些拦截,Handler会将日志内容输出到指定位置(日志文件、控制台等);Handler在输出日
      志时会使用Layout对日志内容进行排版,之后再输出到指定的位置。
    复制代码

    2:入门案例

    复制代码
        @Test
        public void demoA() {
            // 1.创建JUL Logger对象无法传入class对象,只能传如:"cn.xw.testDemo"
            Logger logger = Logger.getLogger(TestDemo.class.getName());
            // 2.输出日志的两种方式,其中日志级别共有7种,还有2种特殊级别
            logger.severe("[SEVERE 1000] 表明程序严重的失败或错误");
            logger.warning("[WARNING 900] 表明潜在的问题");
            // 带有占位符的日志打印
            String name = "蚂蚁小哥", age = "24";
            logger.log(Level.INFO, "姓名:{0} 年龄:{1}", new Object[]{name, age});
        }
    复制代码

    3:日志级别

    复制代码
    JUL内置工七种日志级别,另外有两种特殊级别(共9个)
    java.util.logging.Level中定义了七种基本的日志级别:
        SEVERE【致命级别 级别码:1000】(最高级别):表明程序严重的失败或错误
        WARNING【警告级别 级别码:900】:表明潜在的问题
        INFO【信息级别 级别码:800】(默认):通过消息会被输入到控制台,所以信息级别一般记录对最终用户或系统管理员有用的信息
        CONFIG【配置级别 级别码:700】:用于表明系统静态配置的信息
        FINE【调试级别 级别码:500】:输出开发人员关注的信息
        FINER【调试级别 级别码:400】:输出进入方法或者出入方法的一些信息和异常捕捉
        FINEST【调试级别 级别码:300】(最低等级):输出最细的信息
    java.util.logging.Level中定义了两种基本的日志级别:
        OFF:可用于关闭日志记录
        ALL:启用所有消息的日志记录
    
    注:默认的日志日志级别由RootLogger决定的所有的Logger对象默认都会继承并使用RootLogger所提供的控制台输出处理器
        对象ConsoleHandler同时,RootLogger的默认日志输出等级为INFO,则所有未经配置的Logger默认也是使用该日志级别。
    跟踪代码:
        Logger logger = Logger.getLogger(TestDemo.class.getName());
        ==>进入getLogger
        return demandLogger(name, null, Reflection.getCallerClass());
        ==>进入demandLogger
        LogManager manager = LogManager.getLogManager();
        ==>进入getLogManager
        manager.ensureLogManagerInitialized();
        ==>进入ensureLogManagerInitialized
        owner.readPrimordialConfiguration();    //读取配置文件 logging.properties
        owner.rootLogger = owner.new RootLogger();
        owner.addLogger(owner.rootLogger);
        //判断日志级别不存在的话则默认defaultLevel
        // private final static Level defaultLevel = Level.INFO; //默认就是INFO
        if (!owner.rootLogger.isLevelInitialized()) {
            owner.rootLogger.setLevel(defaultLevel);
        }
    复制代码

    4:自定义日志(控制台记录和日志文件记录)

      自定义日志通常会在单独的配置文件中去配置Logger的日志等级和处理器类型等,但作为入门,需要了解如何在Java代码中,通过更改Logger日志级别和配置自定义ConsoleHandler的方式,去影响日志输出。如果不希望Logger对象使用RootLogger中的日志级别进行输出,则需要对Logger进行以下配置:重新设置Logger的日志输出等级;重新配置Logger的处理器Handler类型,并不再使用RootLogger中提供的默认处理器。

    复制代码
        @Test
        public void demoA() throws IOException {
            //获取日志记录器
            Logger logger = Logger.getLogger(TestDemo.class.getName());
            //设置false;指定此记录器是否应将其输出发送到其父记录器(是否继承父记录器配置)(必须设置)
            logger.setUseParentHandlers(false);
            //创建日志格式化组件对象
            //若想自定义格式化则通过simpleFormatter.format(java.util.logging.LogRecord record)
            SimpleFormatter simpleFormatter = new SimpleFormatter();
            //创建Handler(ConsoleHandler为控制台输出处理器)
            ConsoleHandler consoleHandler = new ConsoleHandler();
            //创建文件处理器Handler  fileLog.log文件名称
            FileHandler fileHandler = new FileHandler("./fileLog.log");
            //设置处理器中的内容输出格式及输出级别
            consoleHandler.setFormatter(simpleFormatter);
            consoleHandler.setLevel(Level.CONFIG);
            fileHandler.setFormatter(simpleFormatter);
            fileHandler.setLevel(Level.CONFIG);
            //设置记录器中的处理器(控制台记录器、文件记录器)及记录器的输出级别
            logger.addHandler(consoleHandler);
            logger.addHandler(fileHandler);
            logger.setLevel(Level.CONFIG);
            //输出打印
            logger.severe("[SEVERE 1000] 表明程序严重的失败或错误");
            logger.warning("[WARNING 900] 表明潜在的问题");
            logger.info("[INFO 800] 信息级别,通过消息会被输入到控制台,所以信息级别一般记录对最终用户或系统管理员有用的信息");
            logger.config("[CONFIG 700] 配置级别,用于表明系统静态配置的信息"); //这时默认级别为 CONFIG
            logger.fine("[FINE 500] 输出开发人员关注的信息");
            logger.finer("[FINER 400] 输出进入方法或者出入方法的一些信息和异常捕捉");
            logger.finest("[FINEST 300] 输出最细的信息");
        }
    复制代码

    5:JUL(Logging)父子关系

      从上面我们可以看出,默认日志级别为INFO,但是更改日志级别为CONFIG时输出还是INFO,未起效果;这是因为我们创建Logger对象时继承并使用了RootLogger中的日志等级和处理器对象(子类无法覆盖父类设置的默认配置)

      我们可以通过如下代码来看出Logger日志的继承关系,继承关系就由获取getLogger("xxx.xxx.xxx")中的点来区分继承关系

    复制代码
        @Test
        public void demoA(){
            //创建3个日志记录器分别是 RootManger -> logger1 -> logger2
            //子logger也间接继承RootManger
            Logger logger1 = Logger.getLogger("cn.xw");
            Logger logger2 = Logger.getLogger("cn.xw.TestDemo");
            //对比继承关系
            System.out.println(logger2.getParent() == logger1); // true
    
            //这里我针对 logger1 进行了自定义日志记录编写(所以logger2默认继承 logger1才是正确的)
            // logger1 不在继承RootLogger 所以继承关系就变为 logger1 -> logger2
            logger1.setUseParentHandlers(false);
            SimpleFormatter simpleFormatter = new SimpleFormatter();
            ConsoleHandler consoleHandler = new ConsoleHandler();
            consoleHandler.setFormatter(simpleFormatter);
            consoleHandler.setLevel(Level.CONFIG);
            logger1.addHandler(consoleHandler);
            logger1.setLevel(Level.CONFIG);
            //logger2记录器记录日志
            logger2.severe("[SEVERE 1000] 表明程序严重的失败或错误");
            logger2.warning("[WARNING 900] 表明潜在的问题");
            logger2.info("[INFO 800] 信息级别,通过消息会被输入到控制台,所以信息级别一般记录对最终用户或系统管理员有用的信息");
            logger2.config("[CONFIG 700] 配置级别,用于表明系统静态配置的信息"); //这是要设置的级别为 CONFIG
            logger2.fine("[FINE 500] 输出开发人员关注的信息");
            logger2.finer("[FINER 400] 输出进入方法或者出入方法的一些信息和异常捕捉");
            logger2.finest("[FINEST 300] 输出最细的信息");
        }
    复制代码

    6:JUL使用自定义配置文件

      开发中较为经常的使用的自定义日志方式,是通过logging.properties文件的方式进行配置(这个配置文件默认在jre/lib目录下);我们程序中需要编写
    代码去显示加载logging.properties文件以启用自定义的配置。
      Handler是单独进行配置的,开发人员可以单独定义控制台输出日志的处理器对象ConsoleHandler或文件输出日志的处理器对象FileHandler等;具备相关自定义Handler后,需要将Logger与Handler进行关联,配置文件支持RootLogger或指定名称的Logger与自定义Handler进行关联;无论任何时候,都需要明确日志最终的输出等级,是同时由Logger与其相关联的Handler所决定的。 
    # RootLogger的日志级别(默认INFO),所有的Handler都受限于此日志级别,Handler的日志级别可以比RootLogger的日志级别高
    .level=ALL
    # RootLogger默认的处理器,可以配置多个,所有非手动解除父日志的子日志都将使用这些处理器
    handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler
    
    # ConsoleHandler控制台输出处理器配置
    # 指定ConsoleHandler默认日志级别
    java.util.logging.ConsoleHandler.level=ALL
    java.util.logging.ConsoleHandler.encoding=UTF-8
    
    # FileHandler文件输出处理器配置
    # 指定FileHandler默认日志级别
    java.util.logging.FileHandler.level=ALL
    # 日志文件输出路径(当前运行程序的路径)
    java.util.logging.FileHandler.pattern=./appLog.log
    # 单个日志文件大小,单位是bit,1024bit即为1kb 1024*1024*100 = 100MB
    java.util.logging.FileHandler.limit=1024*1024*100
    # 日志文件数量,如果数量为2,则会生成appLog.log.0文件和appLog.log.1文件,总容量为: (limit * count)bit
    java.util.logging.FileHandler.count=2
    # FileHandler持有的最大并发锁数
    java.util.logging.FileHandler.maxLocks=100
    # 指定要使用的Formatter类的名称,FileHandler默认使用的是XMLFormatter
    java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
    # 涉及中文日志就最好加上编码集
    java.util.logging.FileHandler.encoding=UTF-8
    # 是否以追加方式添加日志内容
    java.util.logging.FileHandler.append=true
    # SimpleFormatter的输出格式配置
    java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
    
    # 自定义日志级别,其中”cn.xw“指的是Logger.getLogger(String name)中的入参name!!!
    com.anhui.handlers=java.util.logging.ConsoleHandler
    com.anhui.level=CONFIG
    # 如果此时不关闭名为cn.hanna的Logger的父日志处理器,则在控制台会同时出现父日志处理器和自定义的处理器,消息将重复输出
    com.anhui.useParentHandlers=false
    logging.properties配置文件
    复制代码
        @Test
        public void demoA() throws IOException {
            //获取LogManager,LogManager是单例对象
            LogManager logManager = LogManager.getLogManager();
            //读取配置文件,并加载应用配置文件logging.properties
            InputStream resourceAsStream = TestDemo.class.getClassLoader().getResourceAsStream("logging.properties");
            logManager.readConfiguration(resourceAsStream);
            //获取日志记录器
            Logger logger = Logger.getLogger("cn.xw.TestDemo");
            //输出打印
            logger.severe("[SEVERE 1000] 表明程序严重的失败或错误");
            logger.warning("[WARNING 900] 表明潜在的问题");
            logger.info("[INFO 800] 信息级别,通过消息会被输入到控制台,所以信息级别一般记录对最终用户或系统管理员有用的信息");
            logger.config("[CONFIG 700] 配置级别,用于表明系统静态配置的信息");
            logger.fine("[FINE 500] 输出开发人员关注的信息");
            logger.finer("[FINER 400] 输出进入方法或者出入方法的一些信息和异常捕捉");
            logger.finest("[FINEST 300] 输出最细的信息"); //这时默认级别为 ALL
            System.out.println("=============================================");
            //指定自定义日志对象的名称,配置文件中对com.anhui名称的Logger进行了特殊配置 自定义
            Logger loggerCustomize = Logger.getLogger("com.anhui");
            loggerCustomize.severe("[SEVERE 1000] 表明程序严重的失败或错误");
            loggerCustomize.warning("[WARNING 900] 表明潜在的问题");
            loggerCustomize.info("[INFO 800] 信息级别,通过消息会被输入到控制台,所以信息级别一般记录对最终用户或系统管理员有用的信息");
            loggerCustomize.config("[CONFIG 700] 配置级别,用于表明系统静态配置的信息"); //这时默认级别为 CONFIG
            loggerCustomize.fine("[FINE 500] 输出开发人员关注的信息");
            loggerCustomize.finer("[FINER 400] 输出进入方法或者出入方法的一些信息和异常捕捉");
            loggerCustomize.finest("[FINEST 300] 输出最细的信息");
        }
    复制代码

    三:Log4j日志框架

      Log4j全称是Log for Java,它是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输出的位置是控制台、文件还是GUI组件,输出位置甚至可以是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;Log4j也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。学习Log4j最好有JUL日志框架基础,因为大部分都类似。

    1:入门案例

    复制代码
    <!--Log4j日志坐标导入-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
    //具体测试代码Demo @Test
    public void demoA() { //创建日志记录器对象 注意是 org.apache.log4j.Logger 包下的 Logger logger = Logger.getLogger(ClientDemo.class); //打印日志 logger.fatal("严重错误,一般造成系统崩溃并终止运行"); logger.error("错误信息,不会影响系统运行"); logger.warn("警告信息,可能会发生问题"); logger.info("运行信息,数据连接,网络连接,IO操作等"); logger.debug("调试信息,一般在开发中使用,记录程序变量传递信息等等"); logger.trace("追踪信息,记录程序所有的流程信息"); } 控制台并未打印日志且打印警告: log4j:WARN No appenders could be found for logger (cn.xw.ClientDemo). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
    复制代码
    复制代码
    用过JUL的都知道,当普通的Logger没有进行额外的配置时,其默认会继承并使用RootLogger的配置。Log4j中也存在RootLogger,
    但由于默认情况下RootLogger不具有任何的Appender(即Handler)。
    如果代码仅为了测试某项功能,并不想编写复杂的log4j.properties,可以使用Log4j提供的默认配置,在获取Logger前添加以下代码加载
    默认配置:BasicConfigurator.configure();
    查看configure()方法的源码: public static void configure() { Logger root = Logger.getRootLogger(); root.addAppender(new ConsoleAppender(new PatternLayout("%r [%t] %p %c %x - %m%n"))); } 早期的源码格式化有点不太现代,但意义明确:为RootLogger对象添加一个Appender,其中Appender的类型为控制器输出的ConsoleAppender 输出的格式使用new PatternLayout("%r [%t] %p %c %x - %m%n") 所以应急解决方案是在 Logger logger = Logger.getLogger(ClientDemo.class); 上加个 BasicConfigurator.configure();
    复制代码

    2:日志级别

    复制代码
    Log4j中的日志级别与JUL的不同,Log4j一共提供了六种日志级别:
        FATAL【严重错误】
            指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了
        ERROR【错误信息】:
            指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别
        WARN【警告信息】:
            表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示
        INFO【运行信息】:
            消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序
            运行的一些重要信息,但是不能滥用,避免打印过多的日志。
        DEBUG【调试信息】【默认级别】
            指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息
        TRACE【追踪信息】:
            记录程序所有的流程信息,很低的日志级别,一般不会使用
        ALL:最低等级的,用于打开所有日志记录。
        OFF:最高等级的,用于关闭所有日志记录。
    
    为了测试默认日志级别,可以使用以下代码测试RootLogger:
    @Test
    public void demoA () {
        // 初始化系统配置,不需要配置文件(这里直接使用默认配置)
        BasicConfigurator.configure();
        // 获取日志记录器对象RootLogger
        final Logger rootLogger = Logger.getRootLogger();
        // 输出配置详情
        System.out.println("Logger level: " + rootLogger.getLevel());
        // 获取全部Appends
        final Enumeration allAppenders = rootLogger.getAllAppenders();
        while (allAppenders != null && allAppenders.hasMoreElements()) {
            final Appender appender = (Appender) allAppenders.nextElement();
            System.out.println("Appender is: " + appender.getClass().getSimpleName());
        }
    }
    //输出:
    //Logger level: TRACE
    //Appender is: ConsoleAppender
    复制代码

    3:Log4j相关组件(重点)

    复制代码
    Log4j主要由Loggers(日志记录器)、Appenders(输出端)和Layout(日志格式化器)组成:
        Loggers:控制日志的输出级别与日志是否输出
        Appenders:指定日志的输出方式(输出到控制台、文件、数据库等)
        Layout:控制日志信息的输出格式
    
    Ⅰ:Logger
        ①:日志记录器,负责收集处理日志记录,Logger的实例命名通常是类的全限定类名。
        ②:Logger的名字大小写敏感,其命名有继承机制。
            例如:name为org.apache.commons的logger会继承name为org.apache的logger。
        注:自log4j 1.2版以来, Logger类已经取代了Category类。对于熟悉早期版本的log4j的人来说,Logger类可以被视为Category类的别名
    Ⅱ:Appenders
        Appender用来指定日志输出到哪个地方,可以同时指定日志的输出目的地。
        Log4j常用的输出目的地有以下几种:
            ConsoleAppender:
                将日志输出到控制台
            FileAppender:
                将日志输出到文件中
            DailyRollingFileAppender:
                将日志输出到一个日志文件,周期为天,即每天输出
            RollingFileAppender:
                将日志信息输出到一个日志文件,并且指定文件的大小,当超过指定大小,会自动将文件重命名,同时产生一个新的文件
            JDBCAppender:
                将日志信息保存到数据库中
    Ⅲ:Layouts
        布局器Layouts用于控制日志输出内容的格式,我们可以使用各种自定义格式输出日志。
        Log4j常用的Layouts有以下几种:
            HTMLLayout:
                格式化日志输出为HTML表格形式
            SimpleLayout:
                简单的日志输出格式,打印的日志格式为info-message
            PatternLayout:
                最强大的格式化方式,可以根据自定义格式输出日志,如果没有指定转换格式,则使用默认的转换格式
    
    补充:PatternLayout中的格式化规则:
        log4j采用类似C语言的printf函数的打印格式格式化日志信息,具体的占位符及其含义如下:
            %m     输出代码中指定的日志信息
            %p     输出优先级,及DEBUG、INFO等
            %n     换行符(Windows平台的换行符为"\n",Unix平台为"\n")
            %r     输出自应用启动到输出该 log 信息耗费的毫秒数
            %c     输出打印语句所属的类的全名
            %t     输出产生该日志的线程全名
            %d     输出服务器当前时间,默认为ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}
            %l     输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)
            %F     输出日志消息产生时所在的文件名称
            %L     输出代码中的行号
            %%     输出一个"%"字符
        可以在%与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如:
            %5c     输出category名称,最小宽度是5,category<5,默认的情况下右对齐
            %-5c     输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
            %.5c     输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格
            %20.30c category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉
    复制代码

    4:自定义配置文件

      使用Log4j不需要特意加载配置文件,对于Maven项目来说,程序会自动扫描resources目录下的log4j.properties配置文件。在下面的案例中我将针对Appenders的五种输出端进行介绍

        @Test
        public void demoA() {
            //创建日志记录器对象 注意是 org.apache.log4j.Logger 包下的
            Logger logger = Logger.getLogger(ClientDemo.class);
            //打印日志
            logger.fatal("严重错误,一般造成系统崩溃并终止运行");
            logger.error("错误信息,不会影响系统运行");
            logger.warn("警告信息,可能会发生问题");
            logger.info("运行信息,数据连接,网络连接,IO操作等");
            logger.debug("调试信息,一般在开发中使用,记录程序变量传递信息等等");
            logger.trace("追踪信息,记录程序所有的流程信息");
        }
    基本代码(代码都是一样的,配置文件才是我们重视的)
    ConsoleAppender         对应自定义名称:console
    FileAppender           对应自定义名称:myFile
    RollingFileAppender       对应自定义名称:rollingFile
    DailyRollingFileAppender    对应自定义名称:dailyFile
    JDBCAppender           对应自定义名称:dbFile
    复制代码
    ### 注:log4j.properties配置文件要放在resources目录下
    # 指定日志的输出级别与输出端(输出级别,自定义输出端名称,自定义输出端名称....)
    log4j.rootLogger = trace,console,myFile,rollingFile,dailyFile,dbFile
    
    ###### 控制台输出配置
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    # 使用PatternLayout来声明日志用自定义格式
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    # 针对PatternLayout来用conversionPattern设置格式
    log4j.appender.console.layout.conversionPattern = %d [%t] %-5p [%c] - %m%n
    
    ###### 文件输出配置
    log4j.appender.myFile = org.apache.log4j.FileAppender
    log4j.appender.myFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.myFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
    # 是否以追加日志的形式添加(默认就是true)
    log4j.appender.myFile.append = true
    # 指定日志的输出路径
    log4j.appender.myFile.file = ./fileLog.log
    # 指定日志的文件编码
    log4j.appender.myFile.encoding = utf-8
    
    ###### 文件自动按大小拆分配置
    log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
    log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.rollingFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
    log4j.appender.rollingFile.append = true
    log4j.appender.rollingFile.file = ./fileRollingFileLog.log
    log4j.appender.rollingFile.encoding = UTF-8
    # 文件内容超过2KB则进行拆分,拆分的最多文件由maxBackupIndex定义
    log4j.appender.rollingFile.maxFileSize = 2KB
    # 文件拆分的数量(这里是3),当文件拆分的数量超限时则最新拆分出的文件覆盖最老的日志文件
    log4j.appender.rollingFile.maxBackupIndex = 3
    
    ###### 文件自动按日期拆分配置
    log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.dailyFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
    log4j.appender.dailyFile.append = true
    log4j.appender.dailyFile.file = ./dailyRollingFile.log
    log4j.appender.dailyFile.encoding = UTF-8
    # 文件拆分的日期 正常按照 '.'yyyy-MM-dd 以天为拆分,这里我以每秒拆分一次
    log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd HH-mm-ss
    
    # MySQL输出配置
    log4j.appender.dbFile = org.apache.log4j.jdbc.JDBCAppender
    log4j.appender.dbFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.dbFile.layout.conversionPattern = %p %r %c %t %d{yyyy/MM/dd HH:mm:ss:SSS} %m %l %F %L %% %n
    log4j.appender.dbFile.URL = jdbc:mysql://localhost:3306/log4j?serverTimezone=GMT%2B8&useAffectedRows=true&useSSL=false
    log4j.appender.dbFile.User = root
    log4j.appender.dbFile.Password = 123
    log4j.appender.dbFile.Sql=INSERT INTO log(project_name, create_date, level, category, file_name, thread_name, line, all_category, message) \
      values('log4j_xiaofeng', '%d{yyyy-MM-dd HH:mm:ss}', '%p', '%c', '%F', '%t', '%L', '%l', '%m')
    复制代码
    复制代码
    -- 输出MySQL配置需要在数据库创建表
    CREATE
    TABLE `log` ( `log_id` INT ( 11 ) NOT NULL AUTO_INCREMENT, `project_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '项目名称', `create_date` VARCHAR ( 255 ) DEFAULT NULL COMMENT '创建时间', `level` VARCHAR ( 255 ) DEFAULT NULL COMMENT '优先级', `category` VARCHAR ( 255 ) DEFAULT NULL COMMENT '所在类的全名', `file_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '输出日志消息产生时所在的文件名称 ', `thread_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '日志事件的线程名', `line` VARCHAR ( 255 ) DEFAULT NULL COMMENT '行号', `all_category` VARCHAR ( 255 ) DEFAULT NULL COMMENT '日志事件的发生位置', `message` VARCHAR ( 4000 ) DEFAULT NULL COMMENT '输出代码中指定的消息', PRIMARY KEY ( `log_id` ) );
    复制代码

    5:自定义配置Logger

    复制代码
    如下我创建了两个日志记录器对象
        Logger.getLogger("cn.xw.mapper.StudentMapper");
        Logger.getLogger("cn.xw.mapper.TeacherMapper");
    现在要让特定名称的logger使用特定的配置:
        通过log4j.logger.{loggerName}的配置方式,让指定名为loggerName的logger使用该配置,由于该logger仍然是隶属
       于rootLogger因此输出是累加的形式。如果RootLogger使用了ConsoleAppender,同时自定义的Logger也使用了
       ConsoleAppender,此时控制台将输出两次日志记录,一次为自定义Logger继承自RootLogger的输出,另一次则为自定义
       Logger自身的输出。但日志等级level则取决于子日志Logger为准。    例:此时RootLogger和Logger同时使用了ConsoleAppender,但输出等级分别为INFO(父)和WARN(子),此时控制台输出WARN等级
    要求"cn.xw.mapper.StudentMapper"日志输出到控制台;"cn.xw.mapper.TeacherMapper"日志输出到文件
    复制代码
    复制代码
    # 指定rootLogger根日志的输出级别与输出端
    log4j.rootLogger = trace,console
    
    #注:下面两个自定义的Logger都会继承rootLogger,所以StudentMapper会输出控制台2次,级别的话会取子级别”debug“
    # 自定义Logger用来匹配 Logger.getLogger("cn.xw.mapper.StudentMapper");
    log4j.logger.cn.xw.mapper.StudentMapper = debug,console
    # 自定义Logger用来匹配 Logger.getLogger("cn.xw.mapper.TeacherMapper");
    log4j.logger.cn.xw.mapper.TeacherMapper = info,myFile
    
    # 控制台输出Appender
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.conversionPattern = %d [%t] %-5p [%c] - %m%n
    
    # 文件输出Appender
    log4j.appender.myFile = org.apache.log4j.FileAppender
    log4j.appender.myFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.myFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
    log4j.appender.myFile.append = true
    log4j.appender.myFile.file = ./fileLog.log
    log4j.appender.myFile.encoding = utf-8
    复制代码
    @Test
    public void demoA() {
        //创建日志记录器对象 注意是 org.apache.log4j.Logger 包下的 cn.xw.mapper.StudentMapper
        Logger logger = Logger.getLogger("cn.xw.mapper.StudentMapper");
        logger.fatal("严重错误,一般造成系统崩溃并终止运行");
        logger.error("错误信息,不会影响系统运行");
        logger.warn("警告信息,可能会发生问题");
        logger.info("运行信息,数据连接,网络连接,IO操作等");
        logger.debug("调试信息,一般在开发中使用,记录程序变量传递信息等等");
        logger.trace("追踪信息,记录程序所有的流程信息");
        // cn.xw.mapper.TeacherMapper
        Logger logger1 = Logger.getLogger("cn.xw.mapper.TeacherMapper");
        logger1.fatal("严重错误,一般造成系统崩溃并终止运行");
        logger1.error("错误信息,不会影响系统运行");
        logger1.warn("警告信息,可能会发生问题");
        logger1.info("运行信息,数据连接,网络连接,IO操作等");
        logger1.debug("调试信息,一般在开发中使用,记录程序变量传递信息等等");
        logger1.trace("追踪信息,记录程序所有的流程信息");
    }
    /** 打印信息
    2022-02-09 15:45:49,172 [main] FATAL [cn.xw.mapper.StudentMapper] - 严重错误,一般造成系统崩溃并终止运行
    2022-02-09 15:45:49,172 [main] FATAL [cn.xw.mapper.StudentMapper] - 严重错误,一般造成系统崩溃并终止运行
    2022-02-09 15:45:49,172 [main] ERROR [cn.xw.mapper.StudentMapper] - 错误信息,不会影响系统运行
    2022-02-09 15:45:49,172 [main] ERROR [cn.xw.mapper.StudentMapper] - 错误信息,不会影响系统运行
    2022-02-09 15:45:49,173 [main] WARN  [cn.xw.mapper.StudentMapper] - 警告信息,可能会发生问题
    2022-02-09 15:45:49,173 [main] WARN  [cn.xw.mapper.StudentMapper] - 警告信息,可能会发生问题
    2022-02-09 15:45:49,173 [main] INFO  [cn.xw.mapper.StudentMapper] - 运行信息,数据连接,网络连接,IO操作等
    2022-02-09 15:45:49,173 [main] INFO  [cn.xw.mapper.StudentMapper] - 运行信息,数据连接,网络连接,IO操作等
    2022-02-09 15:45:49,173 [main] DEBUG [cn.xw.mapper.StudentMapper] - 调试信息,一般在开发中使用,记录程序变量传递信息等等
    2022-02-09 15:45:49,173 [main] DEBUG [cn.xw.mapper.StudentMapper] - 调试信息,一般在开发中使用,记录程序变量传递信息等等
    注:这下面就是继承了RootLogger输出的 TeacherMapper信息 ,它子对象是输出到文件里,
    2022-02-09 15:45:49,173 [main] FATAL [cn.xw.mapper.TeacherMapper] - 严重错误,一般造成系统崩溃并终止运行
    2022-02-09 15:45:49,173 [main] ERROR [cn.xw.mapper.TeacherMapper] - 错误信息,不会影响系统运行
    2022-02-09 15:45:49,173 [main] WARN  [cn.xw.mapper.TeacherMapper] - 警告信息,可能会发生问题
    2022-02-09 15:45:49,174 [main] INFO  [cn.xw.mapper.TeacherMapper] - 运行信息,数据连接,网络连接,IO操作等
    Process finished with exit code 0
    */
    测试代码信息
    复制代码
    这时就有人问了,自定义的LoggerName是"cn.xw.mapper.StudentMapper"使用ConsoleAppender输出了两次,一次是继承父Logger,一次是
    自己原本的;若想输出一次则有两种方式:
    ①:摒弃RootLogger的输出,即断开指定Logger与RootLogger的继承关系
        # 自定义Logger用来匹配 Logger.getLogger("cn.xw.mapper.StudentMapper");
        log4j.logger.cn.xw.mapper.StudentMapper = debug,console
        # 名为“cn.xw.mapper.StudentMapper”的Logger不再继承使用RootLogger中的Appender
        log4j.additivity.cn.xw.mapper.StudentMapper = false
    ②:摒弃Logger的输出,即指定名称的Logger直接使用RootLogger关联的Appender,子Logger不再额外指定和RootLogger相同的Appender
        # 自定义Logger用来匹配 Logger.getLogger("cn.xw.mapper.StudentMapper");
        log4j.logger.cn.xw.mapper.StudentMapper = debug
    复制代码

    6:日志输出详细信息

    复制代码
        @Test
        public void demoA() {
            //设置内部调试模式,开启此功能会在控制台详细记录Log4j在记录日志时打印的日志,记录自己
            LogLog.setInternalDebugging(true);
            //创建日志记录器对象
            Logger logger = Logger.getLogger("cn.xw.mapper.StudentMapper");
            logger.fatal("严重错误,一般造成系统崩溃并终止运行");
        }
    复制代码

    四:JCL日志门面框架

      JCL(Jakarta Commons Logging)是Apache提供的一个通用日志API(日志门面)。用户可以自由选择第三方的日志组件作为具体实现,像log4j或者JDK自带日志框架(JUL),选择Log4j或JUL的第三方日志框架后,common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。当然common-logging内部有一个Simple logger 的简单实现,但是功能很弱。所以使用common-logging,通常都是配合着log4j以及其它日志框架来使用。使用它的好处就是我们代码依赖common-logging的API而非log4j的API,避免了和具体的日志API直接耦合,在有必要时,可以更改日志实现的第三方库。

    JCL有两个基本抽象类:Log(基本记录器)和LogFactory(负责创建Log实例)

    1:JCL入门及总结

    复制代码
    <!--JCL门面坐标依赖-->
        <dependency>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
          <version>1.2</version>
        </dependency>
    
    // 入门代码
    public class JCLClient {
        @Test
        public void demoA() {
            //创建日志对象;LogFactory是一个抽象类,LogFactoryImpl才是具体实现
            Log logger = LogFactory.getLog(JCLClient.class);
            //日志记录输出
            logger.fatal("严重错误,一般造成系统崩溃并终止运行");
            logger.error("错误信息,不会影响系统运行");
            logger.warn("警告信息,可能会发生问题");
            logger.info("运行信息,数据连接,网络连接,IO操作等"); // 默认级别INFO(和JUL默认级别一样)
            logger.debug("调试信息,一般在开发中使用,记录程序变量传递信息等等");
            logger.trace("追踪信息,记录程序所有的流程信息");
        }
    }

      二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
      严重: 严重错误,一般造成系统崩溃并终止运行
      二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
      严重: 错误信息,不会影响系统运行
      二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
      警告: 警告信息,可能会发生问题
      二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
      信息: 运行信息,数据连接,网络连接,IO操作等

    复制代码

      通过上面的入门案例我们可以发现,打印出来的日志和JUL的日志样式是一样的,那么我猜测它底层的日志实现使用的是JUL,因为我并未导入Log4j包,那导入了Log4j坐标是什么样的呢?测试一下看看

    复制代码
    <!--Log4j日志框架坐标-->
        <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.17</version>
        </dependency>
    
    //导入后我们编写的代码不用改变,直接可以运行,日志打印出log4j:WARN No appenders could be found for logger (cn.xw.JCLClient).
    log4j:WARN Please initialize the log4j system properly.
    log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.这就证明了,导入Log4j日志框架坐标后JCL会动态查找到Log4j并切换Log4j作为底层日志打印的第三方框架
    这里在resources目录下编写一个log4j.properties配置文件即可

    总结:我们为什么要使用日志门面: ①:面向接口开发,不再依赖具体的实现类。减少代码的耦合 ②:项目通过导入不同的日志实现类,可以灵活的切换日志框架 ③:统一API,方便开发者学习和使用 ④:统一配置便于项目日志的管理
    复制代码

    2:源码分析

      查看Log接口并光标对着Log接口按下CTRL+H来查看具体的实现类

    AvalonLoggerJdk13LumberjackLoggerJdk14LoggerLog4JLoggerLogKitLoggerNoOpLogSimpleLog

    进入JDK14Logger类:

    复制代码
    //注意这里引入的类是JDK自带的日志JUL的Logger类
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    public class Jdk14Logger implements Log, Serializable {
        protected static final Level dummyLevel = Level.FINE;
        protected transient Logger logger = null;//默认为null
        public Jdk14Logger(String name) {
            this.name = name;
            //调用了自身的getLogger()方法
            logger = getLogger();
        }
        //1、获取Logger实例方法
        public Logger getLogger() {
            if (logger == null) {
                //重要:这里调用的是java.util.logging.Logger(即JUL)的获取实例方法
                logger = Logger.getLogger(name);
            }
            return logger;
        }
        //info日志等级调用
        public void info(Object message) {
            //实际上就是调用的JUL的INFO日志等级的Log方法
            log(Level.INFO, String.valueOf(message), null);
        }
        //fatal:我们之前在Log4j中看到是最高等级,这里也用来表示最高的意思
        public void fatal(Object message, Throwable exception) {
            //实际上就是调用JUL的SERVER日志等级(最高)的Log方法
            log(Level.SEVERE, String.valueOf(message), exception);
        }
    }
    复制代码

      看到这里其实心里就清楚了,原来就是外面套了个方法呀,通过继承Log接口来让Log接口统一管理各个日志框架,从而达到切换日志框架不改变代码的操作。那对于JCL中Log4j的方法我们也差不多知道了是如何实现了的。

    //注意这里使用的是Log4j的日志
    import org.apache.log4j.Logger;
    import org.apache.log4j.Priority;
    import org.apache.log4j.Level;
    
    public class Log4JLogger implements Log, Serializable {
    
            //默认为null
            private transient volatile Logger logger = null;
            private static final String FQCN = Log4JLogger.class.getName();
            
            //类被加载时进行初始化
            static {
                if (!Priority.class.isAssignableFrom(Level.class)) {
                    throw new InstantiationError("Log4J 1.2 not available");
                }
                
                Priority _traceLevel;
                try {
                } catch(Exception ex) {
                    _traceLevel = Level.DEBUG;
                }
                traceLevel = _traceLevel;
            }
        
            //无参构造器
            public Log4JLogger() {
                name = null;
            }
        
            //有参构造器
            public Log4JLogger(String name) {
                this.name = name;
                //通过调用自身类的getLogger()获取
                this.logger = getLogger();//见1
            }
        
           //1、通过org.apache.log4j.Logger的getLogger()来获取实例
           public Logger getLogger() {
                Logger result = logger;
                if (result == null) {
                    synchronized(this) {
                        result = logger;
                        if (result == null) {
                            logger = result = Logger.getLogger(name);
                        }
                    }
                }
                return result;
            }
            
            //fatal等级方法就是调用的Log4j的FATAL等级,是一致的
            public void fatal(Object message) {
                getLogger().log(FQCN, Level.FATAL, message, null);
            }
    }
    这里是导入Log4j后会实例Log4jLogger对象

      那为什么添加一个Log4j坐标底层就切换到了Log4j的日志框架输出打印,去除Log4j坐标就变成JUL日志框架打印了,别急我们这就要查看LogFactory.getLog(xxx.class)的getLog方法了,通过它获取不同的Log对象

    复制代码
    // LogFactory抽象对象
    public abstract class LogFactory {
        ①:获取Log对象实例
        public static Log getLog(Class clazz) throws LogConfigurationException {
            //获取具体实例
            return getFactory().getInstance(clazz);
        }
    }
    
    // LogFactoryImpl是LogFactory具体实现类
    public class LogFactoryImpl extends LogFactory {
    ...
        //该数组中包含了对应的全限定类名
        private static final String[] classesToDiscover = new String[]
        {"org.apache.commons.logging.impl.Log4JLogger",
        "org.apache.commons.logging.impl.Jdk14Logger",
        "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
        "org.apache.commons.logging.impl.SimpleLog"};
    ...
        ②:获取Log实例
        public Log getInstance(Class clazz) throws LogConfigurationException {
            //这里传入类的名称
            return this.getInstance(clazz.getName());
        }
    ...
        ③:其中获取到Log实例
        public Log getInstance(String name) throws LogConfigurationException {
            Log instance = (Log)this.instances.get(name);
            if (instance == null) {
                //通过类名获取到Log实例
                instance = this.newInstance(name);
                this.instances.put(name, instance);
            }
            return instance;
        }
    ...
        ④:获取新的实例
        protected Log newInstance(String name) throws LogConfigurationException {
            try {
                Log instance;
                Object[] params;
                //默认为null
                if (this.logConstructor == null) {
                    //重要:查找Log实现类,及获取Log的单个实现类
                    instance = this.discoverLogImplementation(name);
                } else {
                    params = new Object[]{name};
                    instance = (Log)this.logConstructor.newInstance(params);
                }
    
                if (this.logMethod != null) {
                    params = new Object[]{this};
                    this.logMethod.invoke(instance, params);
                }
                //返回获取到的Log实例即可
                return instance;
            } catch (LogConfigurationException var5)
            ...
        }
    
        //⑤:该方法通过数组的顺序来进行实现类实例
        private Log discoverLogImplementation(String logCategory) throws LogConfigurationException {
            ...
            //核心部分:从数组中进行遍历,可见classesToDiscover数组内容(上面),顺序依次为Log4JLogger、Jdk14Logger..
            //其中的result == null则是判断是否获取到了实例,若获取到退出for循环
         //循环根据多种框架实现类的全限定类目去查找获取的Log实例是否存在
    for(int i = 0; i < classesToDiscover.length && result == null; ++i) { result = this.createLogFromClass(classesToDiscover[i], logCategory, true); } if (result == null) { throw new LogConfigurationException("No suitable Log implementation"); } else { //返回指定实例 return result; } } }
    复制代码

      我们在调用LogFactory.getLog()方法时获取了JCL中Log接口的实现类实例,在实现类中的构造器里获取到了真正对应的日志框架Logger实例

    复制代码
    总结:
        1、JCL日志门面中Log是通用接口,LogFactory.getLog()用于获取对应Log的实现类。
        2、Log接口(包含了主要的日志等级方法)的实现类主要的是两个,Jdk14Logger(即JUL,不导任何包默认)和Log4JLogger
            (即Log4j,导入Log4j则切换)。
        3、对于获取Log实例是在LogFactory.getLog()中执行获取的,执行顺序见如下
            "org.apache.commons.logging.impl.Log4JLogger"
            "org.apache.commons.logging.impl.Jdk14Logger"
            "org.apache.commons.logging.impl.Jdk13LumberjackLogger"
            "org.apache.commons.logging.impl.SimpleLog"
        4、在获取到指定Log实例时,即使用构造器会进行各个实现类中的Logger实例的赋值获取(通过对应的日志框架),
            其中的日志等级方法调用的是对应框架的日志等级方法。
    复制代码

    五:SLF4J日志门面框架(重点)

      SLF4J即简单日志门面(Simple Logging Facade for Java),它主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其它日志框架,例如log4j和logback等;当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到;对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。所以我们可以得出SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接。SLF4J和JCL这两个门面框架差不多。

    核心功能:日志框架的绑定、日志框架的桥接

    1:SLF4J入门案例及说明

    复制代码
    <!--SLF4J基本的API坐标依赖;注单独导入这个是没有日志效果-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.35</version>
    </dependency>
    <!--基本的simple,就是刚才说的SLF4J提供的基本日志实现,-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.35</version>
    </dependency>
    复制代码
    复制代码
        @Test
        public void demoA() {
            //通过Logger工厂对象动态获取我们具体导入的日志实现框架(Log4j、Logback、JUL、slf4j-simple)
            Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
            //日志打印方式1
            logger.error("[error  ] 最高级别程序错误");
            logger.warn("[warning] 程序警告不会出现错误");
            logger.info("[info   ] 程序info数据连接,网络连接,IO操作等"); // 默认级别
            logger.debug("[debug  ] 一般在开发中使用,记录程序变量传递信息等等");
            logger.trace("[trace  ] 记录程序所有的流程信息");
            //日志打印方式2
            String name = "蚂蚁小哥", age = "23";
            logger.info("info日志记录:我的姓名是:{} 年龄:{}", name, age);
            //日志打印方式3
            try {
                int a = 1 / 0;
            } catch (Exception e) {
                logger.error("程序发生严重错误!", e);
            }
        }
    
    [main] ERROR cn.xw.Slf4jDemo - [error  ] 最高级别程序错误
    [main] WARN cn.xw.Slf4jDemo - [warning] 程序警告不会出现错误
    [main] INFO cn.xw.Slf4jDemo - [info   ] 程序info数据连接,网络连接,IO操作等
    [main] INFO cn.xw.Slf4jDemo - info日志记录:我的姓名是:蚂蚁小哥 年龄:23
    [main] ERROR cn.xw.Slf4jDemo - 程序发生严重错误!
    java.lang.ArithmeticException: / by zero
        at cn.xw.Slf4jDemo.demoA(Slf4jDemo.java:30)
        ....
    复制代码

    2:日志级别

      这里我就简单和大家阐述一下了,因为大同小异,和上面的日志框架的级别差不多

    复制代码
    ERROR【错误信息】:
        用来记录运行时的错误信息,表示程序运行过程中出现了需要被解决的问题,往往是一些异常。使用error日志的时候
        一般会将详细的异常出现的原因记录。如果不想输出太多的日志,可以使用这个级别
    WARN【警告信息】:
        用来记录一些警告信息。警告信息表示,程序进入了一个特殊的状态,在该状态下程序可以继续运行,但是不建议让程序进入
        该状态,因为该状态可能导致结果出现问题;有些信息不是错误信息,但是也要给程序员的一些提示
    INFO【运行信息】:
        消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序
        运行的一些重要信息,但是不能滥用,避免打印过多的日志。
    DEBUG【调试信息】:
        这类日志往往用在判断是否有出现bug的场景,且往往记录了代码运行的详细信息,比如方法调用传入的参数信息,适用开发环境
    TRACE【追踪信息】:
        最低优先级的日志,一般用来追踪详细的程序运行流,比如程序的运行过程中,运行到了哪一个方法,进入了哪一条分支。
        通过trace程序的运行流程,可以判断程序是否按照期望的逻辑在运行,一般不会使用
    复制代码

    3:常见日志框架的绑定集成

      通过前面介绍知道SLF4J它只是个门面框架,只提供API接口而不提供底层实现,所以我们就要绑定具体的日志实现框架;在入门案例中我们已经绑定了SLF4J官方的自己编写的入门日志框架,这里就不在过多介绍,因为功能单调;

    复制代码
    针对上面的编号进行介绍:
    ①:SLF4J
        单独导入slf4j-api是没有日志打印的效果,只会打印几句提示信息,提示未绑定日志实现,因为底层没有绑定具体的日志框架
    ②:SLF4J+logback
        底层绑定logback日志实现框架
    ③:SLF4J+reload4j(Log4j)
        底层绑定Log4j日志实现框架(reload4j是Log4j的升级版,因为之前Log4j出现了重大漏洞)
    ④:SLF4J+JUL
        底层绑定Java自带的JUL日志实现框架
    ⑤:SLF4J+Simple
        底层绑定SLF4J官方推出的基本日志实现框架
    ⑥:SLF4J+nop
        关闭一切日志输出信息
    下面按照顺序依次介绍及使用
    复制代码

     ①:SLF4J

    复制代码
    <!--SLF4J基本的API坐标依赖;注单独导入这个是没有日志效果-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.35</version>
    </dependency>
    
    //测试代码(这个测试代码是通用的,因为使用的是门面API,后面的案例可以不要改变测试代码)
    @Test
    public void demoA() {
        //通过Logger工厂对象动态获取我们具体导入的日志实现框架(Log4j、Logback、JUL、slf4j-simple)
        Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
        //日志打印方式
        logger.error("[error  ] 最高级别程序错误");
        logger.warn("[warning] 程序警告不会出现错误");
        logger.info("[info   ] 程序info数据连接,网络连接,IO操作等");
        logger.debug("[debug  ] 一般在开发中使用,记录程序变量传递信息等等");
        logger.trace("[trace  ] 记录程序所有的流程信息");
    }
    
    <!--具体打印-->
    <!--提示我们说没有绑定具体的日志实现-->
    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
    复制代码

    ②:SLF4J+LogBack

    复制代码
    <!--集成logback 在maven中只要导入logback-classic即可,code会依赖传递-->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.10</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.10</version>
    </dependency>
    
    <!--定义一个logback.xml文件放到resources目录下,没有当前文件则走默认配置,后面章节会详细讲解logback-->
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration debug="false" scan="false" scanPeriod="30 seconds">
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
        <root level="DEBUG">
            <appender-ref ref="STDOUT"/>
        </root>
    </configuration>
    
    <!--打印效果-->
    17:35:04.120 [main] ERROR cn.xw.Slf4jDemo - [error  ] 最高级别程序错误
    17:35:04.124 [main] WARN  cn.xw.Slf4jDemo - [warning] 程序警告不会出现错误
    17:35:04.125 [main] INFO  cn.xw.Slf4jDemo - [info   ] 程序info数据连接,网络连接,IO操作等
    17:35:04.126 [main] DEBUG cn.xw.Slf4jDemo - [debug  ] 一般在开发中使用,记录程序变量传递信息等等
    复制代码

    ③:SLF4J+reload4j(Log4j)

    复制代码
    <!--reload4j适配器  替代Log4j-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-reload4j</artifactId>
        <version>1.7.35</version>
    </dependency>
    <!--reload4j(原名Log4j)-->
    <dependency>
        <groupId>ch.qos.reload4j</groupId>
        <artifactId>reload4j</artifactId>
        <version>1.2.18.5</version>
    </dependency>
    
    <!--创建log4j.properties放在resources目录下(必须有这个文件,否则无法使用)-->
    log4j.rootLogger=DEBUG,console
    #输出到控制台
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    #设置输出样式
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    #日志输出信息格式为
    log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
    
    <!--日志打印-->
    2022-02-12 17:59:30,428 [main] ERROR [cn.xw.Slf4jDemo] - [error  ] 最高级别程序错误
    2022-02-12 17:59:30,429 [main] WARN  [cn.xw.Slf4jDemo] - [warning] 程序警告不会出现错误
    2022-02-12 17:59:30,429 [main] INFO  [cn.xw.Slf4jDemo] - [info   ] 程序info数据连接,网络连接,IO操作等
    2022-02-12 17:59:30,429 [main] DEBUG [cn.xw.Slf4jDemo] - [debug  ] 一般在开发中使用,记录程序变量传递信息等等
    复制代码

    ④:SLF4J+JUL

    复制代码
    <!--JUL适配器-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jdk14</artifactId>
        <version>1.7.35</version>
    </dependency>
    
    // 打印日志
    二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA
    严重: [error  ] 最高级别程序错误
    二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA
    警告: [warning] 程序警告不会出现错误
    二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA
    信息: [info   ] 程序info数据连接,网络连接,IO操作等
    复制代码

    ⑤:SLF4J+Simple

      这个可以参考入门案例

    ⑥:SLF4J+nop

    复制代码
    <!--slf4j-nop禁用日志,导入这个坐标即可-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-nop</artifactId>
        <version>1.7.35</version>
    </dependency>
    复制代码

    ⑦:注意事项

    复制代码
    坐标导入不能出现冲突情况,比如既导入了Log4j作底层实现也导入了LogBack作底层实现,这样就会产生冲突,会选择其中一个,
    选择规律是先导入的被选择
    这时日志打印出多个实现框架,并在后面选择出当前正在使用的日志打印框架是logback
    SLF4J: Class path contains multiple SLF4J bindings.
    SLF4J: Found binding in [jar:file:/I:/maven_repository/ch/qos/logback/logback-classic/1.2.10/logback-classic-1.2.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]
    SLF4J: Found binding in [jar:file:/I:/maven_repository/org/slf4j/slf4j-reload4j/1.7.35/slf4j-reload4j-1.7.35.jar!/org/slf4j/impl/StaticLoggerBinder.class]
    SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
    SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
    复制代码

    4:SLF4J日志桥接技术

      假设当初的项目使用的是非SLF4J的日志技术(这里我就以当初使用Log4j来说),但是在不久的将来感觉这些日志框架不是太好,这时我想切换SLF4J门面+LogBack日志框架来实现具体的日志记录,那么当初在项目中引用的Log4j坐标将不能再继续使用下去了,因为我想把整个项目中的日志记录全部交由最终的LogBack输出,如果Log4j引用的坐标不剔除,那么最终以前的日志打印还是走的Log4j输出

    复制代码
    <!--当初使用Log4j日志来打印日志-->
    <!--reload4j(原名Log4j)-->
    <dependency>
        <groupId>ch.qos.reload4j</groupId>
        <artifactId>reload4j</artifactId>
        <version>1.2.18.5</version>
    </dependency>
    
    <!--后来把整个项目改变为SLF4J门面+LogBack日志框架-->
    <!--SLF4J基本的API坐标依赖;注单独导入这个是没有日志效果-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.35</version>
    </dependency>
    <!--集成logback 在maven中只要导入logback-classic即可,code会依赖传递-->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.10</version>
    </dependency>
    <!--reload4j的桥接器,桥接到当前SLF4J门面中,还需把原来的reload4j注释掉-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>log4j-over-slf4j</artifactId>
        <version>1.7.35</version>
    </dependency>
    复制代码

    六:Logback日志框架

    复制代码
    Logback 是由log4j创始人设计的另一个开源日志组件;可分为以下几个模块:
        logback-core:
            其它两个模块的基础模块
        logback-classic:
            它是log4j的一个改良版本,同时它完整实现了slf4j API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging
        logback-access:
            访问模块与Servlet容器集成提供通过Http来访问日志的功能
    详细学习Logback请参考 logback系列教程 (51gjie.com)
    复制代码

    1:Logback取代Log4j的理由

    复制代码
    1:更快的实现:Logback的内核重写了,在一些关键执行路径上性能提升10倍以上。而且logback不仅性能提升了,初始化内存加载也更小了。
    2:非常充分的测试:Logback经过了几年,数不清小时的测试。Logback的测试完全不同级别的。
    3:Logback-classic非常自然实现了SLF4j:Logback-classic实现了SLF4j。在使用SLF4j中,你都感觉不到logback-classic。
        而且因为logback-classic非常自然地实现了slf4j,所以切换到log4j或者其他,非常容易,只需要提供成另一个jar包就OK,
        根本不需要去动那些通过SLF4J API实现的代码。
    4:非常充分的文档 官方网站有两百多页的文档。
    5:自动重新加载配置文件,当配置文件修改了,Logback-classic能自动重新加载配置文件。扫描过程快且安全,它并不需要另外创建一个
      扫描线程。这个技术充分保证了应用程序能跑得很欢在JAVA EE环境里面。 6:谨慎的模式和非常友好的恢复,在谨慎模式下,多个FileAppender实例跑在多个JVM下,能够安全地写到同一个日志文件。
      RollingFileAppender会有些限制。Logback的FileAppender和它的子类包括RollingFileAppender能够非常友好地从I/O异常
      中恢复。 7:配置文件可以处理不同的情况,开发人员经常需要判断不同的Logback配置文件在不同的环境下(开发,测试,生产)。而这些配置文件仅仅
      只有一些很小的不同,可以通过,和来实现,这样一个配置文件就可以适应多个环境。 8:Filters(过滤器)有些时候,需要诊断一个问题,需要打出日志。在log4j,只有降低日志级别,不过这样会打出大量的日志,会影响应
      用性能。在Logback,你可以继续 保持那个日志级别而除掉某种特殊情况,如alice这个用户登录,她的日志将打在DEBUG级别而其他   用户可以继续打在WARN级别。要实现这个功能只需加4行XML配置。可以参考MDCFIlter 。 9:SiftingAppender(一个非常多功能的Appender):它可以用来分割日志文件根据任何一个给定的运行参数。如,SiftingAppender能够   区别日志事件跟进用户的Session,然后每个用户会有一个日志文件。 10:自动压缩已经打出来的log:RollingFileAppender在产生新文件的时候,会自动压缩已经打出来的日志文件。压缩是个异步过程,   所以甚至对于大的日志文件,在压缩过程中应用不会受任何影响。 11:堆栈树带有包版本:Logback在打出堆栈树日志时,会带上包的数据。 12:自动去除旧的日志文件:通过设置TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory属性,你可以控制已经产生日   志文件的最大数量。如果设置maxHistory 12,那那些log文件超过12个月的都会被自动移除。
    复制代码

    2:Logback入门案例

    复制代码
            <!--Logback基础模块-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>1.2.10</version>
            </dependency>
            <!--logback-classic是log4j改良版本,它完整实现SLF4J API,这样只要导入此坐标就可以和SLF4J API契合-->
            <!--导入此坐标会自带依赖导入一个core基本包-->
            <!--这个依赖直接包含了 logback-core 以及 slf4j-api的依赖-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.10</version>
            </dependency>
    复制代码
    复制代码
    @Test
    public void demoA() {
        //通过Logger工厂对象动态获取我们具体导入的日志实现框架Logback
        //因为导入logback-classic坐标后会自动依赖传递SLF4J-api门面
        Logger logger = LoggerFactory.getLogger(LogbackDemo.class);
        //日志打印方式
        logger.error("[error  ] 最高级别程序错误");
        logger.warn(" [warning] 程序警告不会出现错误");
        logger.info(" [info   ] 程序info数据连接,网络连接,IO操作等");
        logger.debug("[debug  ] 一般在开发中使用,记录程序变量传递信息等等"); // 默认级别
        logger.trace("[trace  ] 记录程序所有的流程信息");
    }
    注:Logback因为和SLF4J API紧密结合,所以日志级别Level和SLF4J一样;
    不指定配置文件则有默认配置文件
    日志级别:error > warning > info > debug > trace
    复制代码

    3:Logback日志文件加载顺序

    复制代码
    logback在启动的时候,会按照下面的顺序加载配置文
    ①:如果java程序启动时指定了logback.configurationFile属性,就用该属性指定的配置文件。
        如java -Dlogback.configurationFile=/path/to/mylogback.xml Test ,
        这样执行Test类的时候就会加载/path/to/mylogback.xml配置
    ②:在classpath中查找 logback.groovy 文件
    ③:在classpath中查找 logback-test.xml 文件
    ④:在classpath中查找 logback.xml 文件
    ⑤:如果是jdk6+,那么会调用ServiceLoader 查找com.qos.logback.classic.spi.Configurator接口的第一个实现类
    ⑥:自动使用ch.qos.logback.classic.BasicConfigurator,在控制台输出日志
    注:上面的顺序表示优先级,使用java -D配置的优先级最高,只要获取到配置后就不会再执行下面的流程。
        相关代码可以看ContextInitializer#autoConfig()方法。
    复制代码

    4:Logback.xml控制台输出配置

    复制代码
    <?xml version="1.0" encoding="UTF-8" ?>
    <configuration>
        <!--定义日志格式参数,后面可以通过${myPattern}使用-->
        <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
    
        <!--定义ConsoleAppender 用于在屏幕上输出日志-->
        <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender">
            <!--显示控制台日志颜色 System.err【红色】 System.out【默认白色】-->
            <target>System.err</target>
            <!--配置日志输出格式,并引用 myPattern 自定的日志格式-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${myPattern}</pattern>
            </encoder>
        </appender>
    
        <!--配置日志记录器并设置日志记录器的打印级别-->
        <root level="ALL">
            <!--引入appender....-->
            <appender-ref ref="consoleAppend"/>
        </root>
    </configuration>
    
    //日志打印
    2022-02-15 16:10:34.466 cn.xw.LogbackDemo [main] ERROR [error  ] 最高级别程序错误
    2022-02-15 16:10:34.470 cn.xw.LogbackDemo [main] WARN   [warning] 程序警告不会出现错误
    2022-02-15 16:10:34.471 cn.xw.LogbackDemo [main] INFO   [info   ] 程序info数据连接,网络连接,IO操作等
    2022-02-15 16:10:34.472 cn.xw.LogbackDemo [main] DEBUG [debug  ] 一般在开发中使用,记录程序变量传递信息等等
    2022-02-15 16:10:34.472 cn.xw.LogbackDemo [main] TRACE [trace  ] 记录程序所有的流程信息
    复制代码

    5:Logback.xml文件和HTML格式输出

    复制代码
    <?xml version="1.0" encoding="UTF-8" ?>
    <configuration>
        <!--定义日志格式参数,后面可以通过${myPattern}使用-->
        <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
        <!--定义日志路径参数,后面可以通过${myPattern}使用-->
        <property name="file_dir" value="D:/logs"/>
    
        <!--定义FileAppender 用于在文件上输出日志-->
        <appender name="fileAppend" class="ch.qos.logback.core.FileAppender">
            <!--日志文件名称-->
            <file>${file_dir}/fileLogback.log</file>
            <!--配置日志输出格式,并引用 myPattern 自定的日志格式-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${myPattern}</pattern>
            </encoder>
        </appender>
    
        <!--定义FileAppender 用于在文件上输出日志-->
        <appender name="fileHtmlAppend" class="ch.qos.logback.core.FileAppender">
            <file>${file_dir}/fileLogback.html</file>
            <!--因为是要输出HTML格式,所以需要布局包装器一下-->
            <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
                <!--设置最终的输出样式-->
                <layout class="ch.qos.logback.classic.html.HTMLLayout">
                    <pattern>%level%d{yyyy-MM-dd HH:mm:ss}%c%M%L%thread%m</pattern>
                </layout>
            </encoder>
        </appender>
        
        <!--配置日志记录器并设置日志记录器的打印级别-->
        <root level="ALL">
            <!--引入appender....-->
            <appender-ref ref="fileAppend"/>
            <appender-ref ref="fileHtmlAppend"/>
        </root>
    </configuration>
    复制代码

    6:Logback.xml文件输出之拆分归档

    复制代码
    <?xml version="1.0" encoding="UTF-8" ?>
    <configuration>
        <!--定义日志格式参数,后面可以通过${myPattern}使用-->
        <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
        <!--定义日志路径参数,后面可以通过${myPattern}使用-->
        <property name="file_dir" value="D:/logs"/>
    
        <!--定义RollingFileAppender 用于在文件上输出日志并拆分归档-->
        <appender name="rollFileAppend" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!--定义日志输出的路径-->
            <!--这里的scheduler.manager.server.home 没有在上面的配置中设定,所以会使用java启动时配置的值-->
            <!--比如通过 java -Dscheduler.manager.server.home=/path/to XXXX 配置该属性-->
            <file>${scheduler.manager.server.home}/fileRollLogback.log</file>
            <!--配置日志输出格式,并引用 myPattern 自定的日志格式-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${myPattern}</pattern>
            </encoder>
            <!--基于大小和时间滚动策略-->
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <!--按照时间和压缩格式声明文件名 压缩文件为.gz-->
                <fileNamePattern>${scheduler.manager.server.home}/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
                <!--按照文件大小拆分 每个文件达到500MB会自动压缩归档-->
                <maxFileSize>500MB</maxFileSize>
            </rollingPolicy>
        </appender>
    
        <!--配置日志记录器并设置日志记录器的打印级别-->
        <root level="ALL">
            <!--引入appender....-->
            <appender-ref ref="rollFileAppend"/>
        </root>
    </configuration>
    复制代码

    7:Logback过滤器及异步打印

    复制代码
    <?xml version="1.0" encoding="UTF-8" ?>
    <configuration>
        <!--定义日志格式参数,后面可以通过${myPattern}使用-->
        <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
    
        <!--定义ConsoleAppender 用于在屏幕上输出日志-->
        <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender">
            <!--显示控制台日志颜色 System.err【红色】 System.out【默认白色】-->
            <target>System.err</target>
            <!--配置日志输出格式,并引用 myPattern 自定的日志格式-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${myPattern}</pattern>
            </encoder>
    
            <!--LevelFilter: 级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,
            过滤器会根据onMath 和 onMismatch接收或拒绝日志。-->
            <!--例如:将过滤器的日志级别配置为INFO,所有INFO级别的日志交给appender处理,非INFO级别的日志,被过滤掉。-->
    <!--        <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
    <!--        <level>INFO</level>-->
    <!--        <onMatch>ACCEPT</onMatch>-->
    <!--        <onMismatch>DENY</onMismatch>-->
    <!--        </filter>-->
            <!--ThresholdFilter: 临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,
            过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。-->
            <!-- 过滤掉所有低于 DEBUG 级别的日志,留下DEBUG及以上级别的日志 -->
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>DEBUG</level>
            </filter>
        </appender>
        
        <!--配置异步日志-->
        <appender name="asyncAppend" class="ch.qos.logback.classic.AsyncAppender">
            <!--要使用到异步的Appender-->
            <appender-ref ref="consoleAppend"/>
        </appender>
        
        <!--配置日志记录器并设置日志记录器的打印级别-->
        <root level="ALL">
            <!--引入appender....-->
            <appender-ref ref="asyncAppend"/>
        </root>
    </configuration>
    复制代码

    8:自定义Logger

    复制代码
    <?xml version="1.0" encoding="UTF-8" ?>
    <configuration>
        <!--定义日志格式参数,后面可以通过${myPattern}使用-->
        <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
        <!--定义ConsoleAppender 用于在屏幕上输出日志-->
        <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender">
            <!--显示控制台日志颜色 System.err【红色】 System.out【默认白色】-->
            <target>System.err</target>
            <!--配置日志输出格式,并引用 myPattern 自定的日志格式-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${myPattern}</pattern>
            </encoder>
        </appender>
    
        <!--additvity="false" 不继承父元素-->
        <logger name="cn.xw" level="INFO" additvity="false">
            <!--自定义logger中配置appender-->
            <appender-ref ref="consoleAppend"/>
        </logger>
    </configuration>
    复制代码

    七:Log4j2日志框架

       具体参考Log4j2

  • 相关阅读:
    要素类WKT文本获取
    Palantir大数据技术在乌克兰战场的应用
    win11解决80端口默认被占用的问题
    wy的leetcode刷题记录_Day33
    redis cluster伪集群搭建及应用
    塔望3W消费战略全案丨大闸蟹上品标准的力量
    【云原生kubernetes从入门到实践系列教程 ]一.docker安装
    JZ38 字符串的排列
    03137计算机网络原 - 物理层
    驱动开发:通过MDL映射实现多次通信
  • 原文地址:https://www.cnblogs.com/antLaddie/p/15867893.html