[Java]日志实战:JCL,slf4j;JUL,logback,log4j,log4j2

本文全面梳理在Java开发中常用的日志框架,注重实战(即将功能实现)。主要面向的读者是架构师养成中的初/中级开发者。由于演示的项目全部采用Maven的方式,所以本文需要的背景知识是了解Maven。所以,如果你想全面认识了解Java日志框架,这篇文章也许很适合你!

本文的主要内容:

  • 日志门面:JCL(Jakarta Commons Logging)、slf4j( Simple Logging Facade for Java)
  • 日志的实现:JUL(java util logging)、logback、log4j、log4j2

为什么需要日志:

  • 我们写代码的过程中,免不了要输出各种调试信息。在没有使用任何日志工具之前,都会使用 System.out.println 来做到。
  • 这么做直观有效,但是有一系列的缺点:不知道这句话是在哪个类,哪个线程里出来的;不知道什么时候前后两句输出间隔了多少时间;无法关闭调试信息,一旦System.out.println多了之后,到处都是输出,增加定位自己需要信息的难度等等。

目录

日志门面

JCL

slf4j

绑定日志框架

桥接旧的日志框架

日志实现

JUL

Logger之间的父子关系

日志级别Level

配置文件

log4j

三大组件

配置文件

自定义Logger

logback

配置输出到控制台

配置输出到控制台

配置输出到HTML

日志拆分&过滤器

异步日志

自定义Logger

log4j2

slf4j和log4j2组合

log4j2配置文件

异步日志


日志门面

什么是门面:日志的规范,不做具体的实现,类似于JDBC中的规范

我们为什么要使用日志门面:

  • 1. 面向接口开发,不再依赖具体的实现类。减少代码的耦合
  • 2. 项目通过导入不同的日志实现类,可以灵活的切换日志框架
  • 3. 统一API,方便开发者学习和使用
  • 4. 统一配置便于项目日志的管理

JCL

Jakarta Commons Logging,是Apache提供的一个通用日志API。

它是为 "所有的Java日志实现"提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常弱(SimpleLog)。所以一般不会单独使用它。他允许开发人员使用不同的具体日志实现工具: Log4j, Jdk自带的日志(JUL)

[Java]日志实战:JCL,slf4j;JUL,logback,log4j,log4j2_第1张图片

总结:

  • JDK内置的日志门面
  • 功能弱,性能差,我们几乎不会使用它

slf4j

简单日志门面(Simple Logging Facade For Java)

  • 给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。
  • 当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。
  • 对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。

官方网站: https://www.slf4j.org/

SLF4J是目前市面上最流行的日志门面。现在的项目中,基本上都是使用SLF4J作为我们的日志系统。

SLF4J日志门面主要提供两大功能:

  • 日志框架的绑定
  • 日志框架的桥接

为什么要使用SLF4J作为日志门面?

  • 使用SLF4J框架,可以在部署时迁移到所需的日志记录框架。
  • SLF4J提供了对所有流行的日志框架的绑定,例如log4j,JUL,Simple logging和NOP。因此可以在部署时切换到任何这些流行的框架。
  • 无论使用哪种绑定,SLF4J都支持参数化日志记录消息。由于SLF4J将应用程序和日志记录框架分离,因此可以轻松编写独立于日志记录框架的应用程序。而无需担心用于编写应用程序的日志记录框架。
  • SLF4J提供了一个简单的Java工具,称为迁移器。使用此工具,可以迁移现有项目,这些项目使用日志框架(如Jakarta Commons Logging(JCL)或log4j或Java.util.logging(JUL))到SLF4J。
  • 总之就是,slf4j是目前最好的日志门面,大家都在用它,我们也就用它。

第一个slf4j实例

创建Maven,添加依赖:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger("net.hackyle"); //看源码知绑定原理
        //打印日志信息
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");

        // 使用占位符输出日志信息
        String name = "jack";
        Integer age = 18;
        logger.info("用户:{},{}", name, age);

        // 将系统异常信息写入日志
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            // e.printStackTrace();
            logger.info("出现异常:", e);
        }
    }
}
//输出:
//        [main] ERROR net.hackyle - error
//        [main] WARN net.hackyle - warn
//        [main] INFO net.hackyle - info
//        [main] INFO net.hackyle - 用户:jack,18
//        [main] INFO net.hackyle - 出现异常:
//        java.lang.ArithmeticException: / by zero
//        at net.hackyle.Main.main(Main.java:23)


    org.slf4j
    slf4j-api
    1.7.27



    org.slf4j
    slf4j-simple
    1.7.27

测试:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger("net.hackyle"); //看源码知绑定原理
        //打印日志信息
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");

        // 使用占位符输出日志信息
        String name = "jack";
        Integer age = 18;
        logger.info("用户:{},{}", name, age);

        // 将系统异常信息写入日志
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            // e.printStackTrace();
            logger.info("出现异常:", e);
        }
    }
}
//输出:
//        [main] ERROR net.hackyle - error
//        [main] WARN net.hackyle - warn
//        [main] INFO net.hackyle - info
//        [main] INFO net.hackyle - 用户:jack,18
//        [main] INFO net.hackyle - 出现异常:
//        java.lang.ArithmeticException: / by zero
//        at net.hackyle.Main.main(Main.java:23)

绑定日志框架

slf4j可以绑定的日志框架:

[Java]日志实战:JCL,slf4j;JUL,logback,log4j,log4j2_第2张图片

注意点:

  1. 绑定时,只需要把相应的POM依赖导入即可,但是只能导入一种具体的日志框架实现
  2. 源码不动
绑定logback:


    org.slf4j
    slf4j-api
    1.7.27



    ch.qos.logback
    logback-classic
    1.2.3



绑定NOP:


    org.slf4j
    slf4j-api
    1.7.27



    org.slf4j
    slf4j-nop
    1.7.25



绑定log4j:需要导入适配器


    org.slf4j
    slf4j-api
    1.7.27




    org.slf4j
    slf4j-log4j12
    1.7.12



    log4j
    log4j
    1.2.17

绑定JDK自带的日志实现:需要导入适配器即可


    org.slf4j
    slf4j-api
    1.7.27




    org.slf4j
    slf4j-jdk14
    1.7.25

桥接旧的日志框架

背景:

  1. 一个项目刚开始时比较小,所以就使用JDK原生的日志框架;
  2. 但是随着项目逐渐扩大,原来的日志框架不满足现在庞大的项目,需要对日志框架进行升级,例如使用logback;
  3. 为了解决这种情况,SLF4J附带了几个桥接模块,这些模块将对log4j,JCL和java.util.logging API的调用重定向,就好像它们是对SLF4J API一样。

桥接过程示意图

[Java]日志实战:JCL,slf4j;JUL,logback,log4j,log4j2_第3张图片

桥接操作步骤:

  1. 先去除之前老的日志框架的依赖
  2. 添加SLF4J提供的桥接组件
  3. 为项目添加SLF4J的具体实现

注意:桥接器(使slf4j关联不同)和适配器不能同时部署

总结:

  • slf4j的绑定:指定日志的实现框架
  • slf4j的桥接:换个新的日志实现
    1. 把老的日志实现从依赖中删除
    2.添加桥接器
    3.添加新的日志实现框架

日志实现

日志门面负责规范API,统一接口。真正实现日志记录的是具体的实现框架们。

日志框架出现的历史顺序:log4j -->JUL--> logback --> log4j2

JUL

Java util Logging

  • 是java原生的日志框架,使用时不需要另外引用第三方类库
  • 相对其他日志框架使用方便,学习简单,能够在小型应用中灵活使用。

[Java]日志实战:JCL,slf4j;JUL,logback,log4j,log4j2_第4张图片

  1. Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger通常时应用程序访问日志系统的入口程序
  2. Appenders:也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。
  3. Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换格式化。Layouts决定了数据在一条日志记录中的最终形式。
  4. Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,我可以将Level和Loggers,Appenders做关联以便于我们过滤消息。
  5. Filters过滤器,根据Level确定哪些数据需要记录,哪些不需要。

工作过程:

  1. 用户使用Logger来进行日志记录,Logger持有若干个Handler,日志的输出操作是由Handler完成的。
  2. 在Handler在输出日志前,会经过Filter的过滤,判断哪些日志级别过滤放行哪些拦截,Handler会将日志内容输出到指定位置(日志文件、控制台等)。
  3. Handler在输出日志时会使用Layout,将输出内容进行排版。

第一个JUL实例

import java.util.logging.Level;
import java.util.logging.Logger;

public class Main {
    public static void main(String[] args) throws Exception {
        //1.获取日志对象记录器
        Logger logger = Logger.getLogger("kyle.Main"); //静态工厂构造器需要传入一个唯一标识,可以采用类全名
        //2.日志记录并输出(如果没有指定输出目标,则默认向控制台输出)
        logger.log(Level.INFO,"第一个日志记录输出");
        //logger.info("第一个日志记录输出"); 等价上一行代码,指定输出级别为INFO

        //通过占位符的形式输出
        String name = "kyle";
        Integer age = 22;
        logger.log(Level.INFO, "用户信息:{0},{1}", new Object[]{name,age});//数组
    }
}
/*
 * 控制台输出:
 * 1月 26, 2021 11:41:59 上午 kyle.Main main
 * 信息: 第一个日志记录输出
 * 1月 26, 2021 11:41:59 上午 kyle.Main main
 * 信息: 用户信息:kyle,22
 */

Logger之间的父子关系

这种父子关系通过树状结构存储,JUL在初始化时会创建一个顶层RootLogger作为所有Logger父Logger,存储上作为树状结构的根节点。

父子结构实例
import java.util.logging.Logger;

public class Main {
    public static void main(String[] args) throws Exception {
        //Logger的父子关系在代码中是根据静态构造器的传入包结构决定的
        Logger logger01 = Logger.getLogger("net.hackyle"); //父Logger
        Logger logger02 = Logger.getLogger("net.hackyle.package01"); //子Logger
        Logger logger03 = Logger.getLogger("net.hackyle.package02"); //子Logger

        //所有日志记录器对象的顶级父元素 class为java.util.logging.LogManager$RootLogger,name为空字符串
        System.out.println(logger01.getParent());

        System.out.println(logger02.getParent() == logger03.getParent()); //true
        System.out.println(logger02.getParent() == logger01); //true
    }
}

日志级别Level

日志级别由java.util.logging.Level类中的七个静态常量确定:

  • SEVERE(最高值):用于记录错误日志
  • WARNING:用于记录警告日志
  • INFO(默认级别):默认情况下,只会输出INFO以及更高级别的日志信息
  • CONFIG:用于记录配置日志
  • FINE:用于记录调试日志
  • FINER:
  • FINEST(最低值):

还有两个特殊的级别:

  • OFF,可用来关闭日志记录。
  • ALL,启用所有消息的日志记录。

输出不同的级别日志
import java.util.logging.Logger;

public class Main {
    public static void main(String[] args) throws Exception {
        //1.获取日志对象记录器
        Logger logger = Logger.getLogger("kyle.Main"); //静态工厂构造器需要传入一个唯一标识,可以采用类全名
        //2.输出不同级别的日志
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info"); //默认情况下,只会输出INFO以及更高级别的日志信息
        logger.config("cofnig");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
}
/*
 * 控制台输出:
 * 1月 26, 2021 11:52:10 上午 kyle.Main main
 * 严重: severe
 * 1月 26, 2021 11:52:11 上午 kyle.Main main
 * 警告: warning
 * 1月 26, 2021 11:52:11 上午 kyle.Main main
 * 信息: info
 */

自定义日志输出级别
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class Main {
    public static void main(String[] args) throws Exception {
        //获取日志对象记录器
        Logger logger = Logger.getLogger("kyle.Main"); //静态工厂构造器需要传入一个唯一标识,可以采用类全名
        //关闭默认的日志级别
        logger.setUseParentHandlers(false);

        //Handler负责将日志做记录,因为是在我们是将日志输出到控制台,所以使用ConsoleHandler
        ConsoleHandler consoleHandler = new ConsoleHandler();
        SimpleFormatter simpleFormatter = new SimpleFormatter(); //格式转换
        
        consoleHandler.setFormatter(simpleFormatter);
        logger.addHandler(consoleHandler);
        
        //配置级别
        logger.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);
        
        //输出到日志文件
        FileHandler fileHandler = new FileHandler("C:/users/kyle/desktop/jul.log");
        fileHandler.setFormatter(simpleFormatter);
        logger.addHandler(fileHandler); //控制台、文件都有日志输出

        //输出不同级别的日志
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info"); //默认情况下,只会输出INFO以及更高级别的日志信息
        logger.config("cofnig");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
}
/*
 * 控制台输出:
 * 1月 26, 2021 12:01:20 下午 kyle.Main main
 * 严重: severe
 * 1月 26, 2021 12:01:20 下午 kyle.Main main
 * 警告: warning
 * 1月 26, 2021 12:01:20 下午 kyle.Main main
 * 信息: info
 * 1月 26, 2021 12:01:20 下午 kyle.Main main
 * 配置: cofnig
 * 1月 26, 2021 12:01:20 下午 kyle.Main main
 * 详细: fine
 * 1月 26, 2021 12:01:20 下午 kyle.Main main
 * 较详细: finer
 * 1月 26, 2021 12:01:20 下午 kyle.Main main
 * 非常详细: finest
 */

配置文件

默认日志配置文件:

  • 根据打断点,开启调试模式,可以得知JUL会加载一个默认的配置文件,其路径为\%JAVAHOME\%jre\lib\logging.properties(JDK1.8)
  • JDK11的路径为:\%JAVAHOME\%config\logging.properties
  • 我们可以根据默认的配置文件格式,修改出自定义的日志配置文件

[Java]日志实战:JCL,slf4j;JUL,logback,log4j,log4j2_第5张图片

建立resources目录,将其指定为“资源目录”;新建myLogging文件,格式上述的默认配置文件:

handlers= java.util.logging.ConsoleHandler

.level= ALL

java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.maxLocks = 100
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

按照配置文件进行日志记录:

import java.io.InputStream;
import java.util.logging.LogManager;
import java.util.logging.Logger;

public class Main {
    public static void main(String[] args) throws Exception {
        //读取日志配置文件
        InputStream in = Main.class.getClassLoader().getResourceAsStream("myLogging.properties");
        //获取日志管理器对象
        LogManager logManager = LogManager.getLogManager();
        //通过日志管理器加载配置文件
        logManager.readConfiguration(in);

        //进行日志记录
        Logger logger = Logger.getLogger("net.hackyle");
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
}

//输出:
1月 26, 2021 12:47:06 下午 kyle.Main main
严重: severe
1月 26, 2021 12:47:06 下午 kyle.Main main
警告: warning
1月 26, 2021 12:47:06 下午 kyle.Main main
信息: info
1月 26, 2021 12:47:06 下午 kyle.Main main
配置: config
1月 26, 2021 12:47:06 下午 kyle.Main main
详细: fine
1月 26, 2021 12:47:06 下午 kyle.Main main
较详细: finer
1月 26, 2021 12:47:06 下午 kyle.Main main
非常详细: finest

总结:

  • Logger及其子父级关系
  • 配置文件自定义输出格式、输出路径、日志记录级别

log4j

  • Log4j是Apache下的一款开源的日志框架
  • 通过在项目中使用 Log4J,我们可以控制日志信息输出到控制台、文件、甚至是数据库中。
  • 我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以更灵活的控制日志的输出过程。方便项目的调试。
  • 官方网站: http://logging.apache.org/log4j/1.2/

第一个log4j程序(版本2.14)

导包(缺一不可):
	log4j-1.2-api-2.14.0.jar
	log4j-api-2.14.0.jar
	log4j-core-2.14.0.jar

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

public class Main {
    public static void main(String[] args) throws Exception {
        // 初始化配置信息,在入门案例中暂不使用配置文件
        BasicConfigurator.configure();

        // 获取日志记录器对象
        Logger logger = Logger.getLogger(Main.class); //注意包的位置

        logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃并终止运行
        logger.error("error"); //错误信息,不会影响系统运行
        logger.warn("warn");   //警告信息,可能会发生问题
        logger.info("info");   //运行信息,数据连接、网络连接、IO 操作等等
        logger.debug("debug"); //默认:调试信息,一般在开发中使用,记录程序变量参数传递信息等等
        logger.trace("trace"); //追踪信息,记录程序所有的流程信息
    }
}
//输出:
//    14:49:27.021 [main] FATAL kyle.Main - fatal
//    14:49:27.031 [main] ERROR kyle.Main - error

第一个log4j程序(版本1.2.17)

新建Maven项目,导入依赖

    log4j
    log4j
    1.2.17


import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

public class Main {
    public static void main(String[] args) {
        BasicConfigurator.configure();
        // 获取日志记录器对象
        Logger logger = Logger.getLogger(Main.class); //注意包的位置

        logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃并终止运行
        logger.error("error"); //错误信息,不会影响系统运行
        logger.warn("warn");   //警告信息,可能会发生问题
        logger.info("info");   //运行信息,数据连接、网络连接、IO 操作等等
        logger.debug("debug"); //默认:调试信息,一般在开发中使用,记录程序变量参数传递信息等等
        logger.trace("trace"); //追踪信息,记录程序所有的流程信息
        //输出:
        //0 [main] FATAL net.hackyle.Main  - fatal
        //1 [main] ERROR net.hackyle.Main  - error
        //1 [main] WARN net.hackyle.Main  - warn
        //1 [main] INFO net.hackyle.Main  - info
        //1 [main] DEBUG net.hackyle.Main  - debug
    }
}

三大组件

  1. Loggers(记录器):日志类别和级别,控制那些信息是需要记录的。
  2. Appenders (输出源):日志要输出的地方,例如输出到C:\users\Admin\desktop\test.log。
  3. Layouts(布局):日志以何种形式(或格式)输出,例如输出为HTML格式。

Loggers(记录器)

记录器功能:

  • 负责收集不同级别和处理日志
  • 使用静态工厂构造器获取,Logger的名字是大小写敏感的,可以是类全名、类的字节码文件
  • 同JUL,其命名有继承机制:例如:name为org.apache.commons的logger会继承name为org.apache的logger。
  • Log4J中有一个特殊的logger叫做“root”,他是所有logger的根,也就意味着其他所有的logger都会直接或者间接地继承自root。root logger可以用Logger.getRootLogger()方法获取。

自log4j 1.2版以来, Logger 类已经取代了Category 类。对于熟悉早期版本的log4j的人来说,Logger 类可以被视为Category 类的别名。

日志级别:

  • fatal 指出每个严重的错误事件将会导致应用程序的退出。
  • error 指出虽然发生错误事件,但仍然不影响系统的继续运行。
  • warn 表明会出现潜在的错误情形。
  • info 一般和在粗粒度级别上,强调应用程序的运行全程。
  • debug 一般用于细粒度级别上,对调试应用程序非常有帮助。
  • trace 是程序追踪,可以用于输出程序运行中的变量,显示执行的流程。

还有两个特殊的级别:

  • OFF,可用来关闭日志记录。
  • ALL,启用所有消息的日志记录。

特性:

  • 只输出级别不低于设定级别的日志信息。例如Loggers级别设定为INFO,则INFO、WARN、ERROR和FATAL级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出。
     

输出源Appenders

  1. org.apache.log4j.ConsoleAppender(控制台)
  2. org.apache.log4j.FileAppender(文件)
  3. org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
  4. org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
  5. org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
  6. org.apache.log4j.JDBCAppender(将日志信息保存到数据库)

布局:

  1. org.apache.log4j.HTMLLayout 格式化日志输出为HTML表格形式
  2. org.apache.log4j.SimpleLayout 简单的日志输出格式化,打印的日志格式为(info - message)
  3. org.apache.log4j.PatternLayout 最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式
  4. org.apache.log4j.TTCCLayout 包含日志产生的时间、线程、类别等信息

配置文件

Log4j支持两种配置文件格式:

  • 一种是XML格式的文件
  • 一种是properties属性文件

第一个Properities配置文件实例

新建Maven项目,导入依赖(版本1.2.17),在resources目录下建立一个log4j.properties:

# rootLogger:顶级父元素配置
# 指定日志级别,指定日志的输出位置,用逗号分隔
log4j.rootLogger = trace,console

# 指定朝输出控制台输出
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定日志的输出格式
log4j.appender.console.layout = org.apache.log4j.PatternLayout
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

public class Main {
    public static void main(String[] args) {
        BasicConfigurator.configure();
        // 获取日志记录器对象
        Logger logger = Logger.getLogger(Main.class); //注意包的位置

        logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃并终止运行
        logger.error("error"); //错误信息,不会影响系统运行
        logger.warn("warn");   //警告信息,可能会发生问题
        logger.info("info");   //运行信息,数据连接、网络连接、IO 操作等等
        logger.debug("debug"); //默认:调试信息,一般在开发中使用,记录程序变量参数传递信息等等
        logger.trace("trace"); //追踪信息,记录程序所有的流程信息
        //输出:
        //fatal
        //0 [main] FATAL net.hackyle.Main  - fatal
        //error
        //1 [main] ERROR net.hackyle.Main  - error
        //warn
        //1 [main] WARN net.hackyle.Main  - warn
        //info
        //1 [main] INFO net.hackyle.Main  - info
        //debug
        //1 [main] DEBUG net.hackyle.Main  - debug
        //trace
        //1 [main] TRACE net.hackyle.Main  - trace
    }
}

自定义Logger

log4j.properties

# rootLogger:顶级父元素配置
# 指定日志级别,指定日志的输出位置,用逗号分隔
# log#4j.rootLogger = trace,console

# 自定义Logger
# 格式:log4j.logger.自己定义名字 = 日志级别,输出位置
# 在继承过程中,日志级别会覆盖,输出位置会继承
# 在名字中,可以是使用点来区分父子关系:
#   log4j.logger.Aa = trace,console 是Bb,Cc的父,Aa的父是rootLogger
#   log4j.logger.Aa.Bb = trace,console 父是Aa
#   log4j.logger.Aa.Cc = trace,console  父是Aa
log4j.logger.Aa = info,file

# 日志文件输出的 appender 对象
log4j.appender.file = org.apache.log4j.FileAppender
# 指定消息格式 layout
log4j.appender.file.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.file.layout.conversionPattern = [%-10p]%r  %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.file.file = C:/users/kyle/desktop/aa.log
# 指定日志文件的字符集
log4j.appender.file.encoding = UTF-8
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class Main {
    public static void main(String[] args) {
        // 获取日志记录器对象
        Logger logger = Logger.getLogger("Aa"); //这里需要传入在配置文件中自定义Logger的名字
        logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃并终止运行
        logger.error("error"); //错误信息,不会影响系统运行
        logger.warn("warn");   //警告信息,可能会发生问题
        logger.info("info");   //运行信息,数据连接、网络连接、IO 操作等等
        logger.debug("debug"); //默认:调试信息,一般在开发中使用,记录程序变量参数传递信息等等
        logger.trace("trace"); //追踪信息,记录程序所有的流程信息
    }
}

执行结果:

[Java]日志实战:JCL,slf4j;JUL,logback,log4j,log4j2_第6张图片

logback

Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。

官方网站:https://logback.qos.ch/index.html

Logback主要分为三个模块:

  • logback-core:其它两个模块的基础模块
  • logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API
  • logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能

logback组件

  • Logger:日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。
  • Appender:用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等。
  • Layout:负责把事件转换成字符串,格式化的日志信息的输出。在logback中Layout对象被封装在encoder中。

logback会依次读取以下类型配置文件:

  1. logback.groovy
  2. logback-test.xml
  3. logback.xml

如果以上三种配置文件均不存在,则会采用默认配置。

官方提供的log4j.properties转换成logback.xml:https://logback.qos.ch/translator/

第一个logback程序

创建Maven项目,导入依赖:



    org.slf4j
    slf4j-api
    1.7.25



    ch.qos.logback
    logback-classic
    1.2.3

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    //slf4j为日志门面,logback为日志实现
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger("net.hackyle");
        //打印日志信息
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");

        // 使用占位符输出日志信息
        String name = "jack";
        Integer age = 18;
        logger.info("用户:{},{}", name, age);

        // 将系统异常信息写入日志
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            // e.printStackTrace();
            logger.info("出现异常:", e);
        }
    }
}
//输出:
//1月 27, 2021 9:56:21 上午 net.hackyle.Main main
//严重: error
//1月 27, 2021 9:56:21 上午 net.hackyle.Main main
//警告: warn
//1月 27, 2021 9:56:21 上午 net.hackyle.Main main
//信息: info
//1月 27, 2021 9:56:21 上午 net.hackyle.Main main
//信息: 用户:jack,18
//1月 27, 2021 9:56:21 上午 net.hackyle.Main main
//信息: 出现异常:
//java.lang.ArithmeticException: / by zero
//at net.hackyle.Main.main(Main.java:23)

配置输出到控制台

新建Maven项目,导入依赖上文中的slf4j和logback依赖

在resources资源文件夹下新建logback.xml配置文件:








    
    
    
    
    
    

    
    
    
        
        System.err
        
        
            ${outputPattern}
        
    

    
    
    
        
    


测试:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    //slf4j为日志门面,logback为日志实现
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger("net.hackyle");
        //打印日志信息
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}
//输出:(检查是否为配置文件中指定的输出格式)
//[ERROR] 2021-01-27 10:26:37.171 net.hackyle main 11 [main] error
//[WARN ] 2021-01-27 10:26:37.178 net.hackyle main 12 [main] warn
//[INFO ] 2021-01-27 10:26:37.179 net.hackyle main 13 [main] info
//[DEBUG] 2021-01-27 10:26:37.181 net.hackyle main 14 [main] debug
//[TRACE] 2021-01-27 10:26:37.181 net.hackyle main 15 [main] trace

配置输出到控制台

新建Maven项目,导入依赖上文中的slf4j和logback依赖

在resources资源文件夹下新建logback.xml配置文件:








    
    
    
    
    
    

    
    

    
    
    
        
        ${log_dir}/logback.log
        
        
            ${outputPattern}
        
    

    
    
    
        
        
        
        
    


测试方式同上一个。

配置输出到HTML

logback.xml


    
    ${log_dir}/logback.html
    
    
        
            
            %-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m
        
    

日志拆分&过滤器

logback.xml


    
    ${log_dir}/roll_logback.log
    
    
        ${outputPattern}
    

    
    
        
        ${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz
        
        1MB
    

    
    
        
        ERROR
        ACCEPT
        DENY
    

异步日志

背景:

  1. 到目前为止,日志的记录都是在主线程中的,日志后的代码要等到日志记录操作完毕后才能继续往下执行。
  2. 如果要实现日志记录完全不占用主线程,而是自己新开一个线程去处理记录日志的操作,这就需要异步日志实现。

logback.xml实现异步日志




    
    System.err
    
    
        ${outputPattern}
    



    
    

自定义Logger

logback.xml


    
    

log4j2

  • Apache Log4j 2是对Log4j的升级版,参考了logback的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升
  • 官网: https://logging.apache.org/log4j/2.x/
  • 目前市面上最主流的日志门面就是SLF4J,虽然Log4j2也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将Log4j2看作是日志的实现,Slf4j + Log4j2应该是未来的大势所趋。

第一个log4j2程序

导入依赖:



    org.apache.logging.log4j
    log4j-api
    2.11.1



    org.apache.logging.log4j
    log4j-core
    2.11.1

测试: 

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Main {
    //log4j2为日志门面,log4j2为日志实现
    public static void main(String[] args) {
        Logger logger = LogManager.getLogger(Main.class);
        logger.fatal("fatal");
        logger.error("error"); //默认级别
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}
//输出:
//ERROR StatusLogger No Log4j 2 configuration file found. 提示没有找到配置文件
//  Using default configuration (logging only errors to the console),
//  or user programmatically provided configurations.
//  Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging.
//  See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2

//11:21:49.214 [main] FATAL net.hackyle.Main - fatal
//11:21:49.219 [main] ERROR net.hackyle.Main - error

slf4j和log4j2组合

导入依赖:



    org.slf4j
    slf4j-api
    1.7.25


    org.apache.logging.log4j
    log4j-core
    2.11.1

log4j2配置文件

由于log4j2是参考这logback来进行设计的,所以在配置文件上有相似的地方。

异步日志

在同步的日志中,日志的操作需要主线程去执行,日志后的业务逻辑需要等到日志操作完成后才会去执行。而在异步日志中,一旦需要日志操作,则新开辟一些线程去处理日志,主线程可以继续执行接下来的业务逻辑代码,不影响整体速度。

Log4j2最牛的地方在于异步输出日志时的性能表现

Log4j2提供了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger(推荐使用),分别对应前面我们说的Appender组件和Logger组件。

配置异步日志需要添加依赖



com.lmax
disruptor
3.3.4

AsyncAppender方式

log4j2.xml配置文件


    
        D:/logs
    
    
        
            
                %d %p %c{1.} [%t] %m%n
            
        
        
            
        
    
    
        
            
        
    

AsyncLogger方式

AsyncLogger才是log4j2 的重头戏,也是官方推荐的异步方式。它可以使得调用Logger.log返回的更快。你可以有两种选择:全局异步和混合异步。

  • 全局异步就是,所有的日志都异步的记录,在配置文件上不用做任何改动,只需要添加一个log4j2.component.properties 配置;

[Java]日志实战:JCL,slf4j;JUL,logback,log4j,log4j2_第7张图片

  • 混合异步就是,你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。但是注意:如果要使用混合异步,那就要把全局异步的关闭。
配置混合异步


    
        D:/logs
    

    
        
            
                %d %p %c{1.} [%t] %m%n
            
        
        
        
            
        
    

    
com.itheima 日志是异步的,root日志是同步的
        
            
        

        
            
        
    


使用异步日志需要注意的问题:

  1. 如果使用异步日志,AsyncAppender、AsyncLogger和全局日志,不要同时出现。要么使用呢全局异步,要么使用混合异步。如果同时出现,性能会和AsyncAppender一致,降至最低。
  2. 设置includeLocation=false ,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。

总结:

  • 日志门面:
    • JCL(Jakarta Commons Logging):JDK内置,弱,基本不用
    • slf4j( Simple Logging Facade for Java):强,流行使用
  • 日志的实现:
    • JUL(java util logging):JDK内置,小项目中用
    • log4j:可以将日志记录到控制台、文件、数据库中,支持properties和xml的文件格式
    • logback:除了log4j的功能外,还支持将日志过滤、日志拆分、异步日志等
    • log4j2:拥有logback的全部功能,性能更强,尤其是异步日志。

  • slf4j+log4j2的组合方式是当下最流行的方式。

 

你可能感兴趣的:(日志,slf4j日志门面,logback,log4j,log4j2)