还不了解,日志框架吗?

2021/12/14

  • 距离过年还有 44天想家😘这两天有一件事闹的 沸沸扬扬的~

  • 某个日志框架,出现了严重的bug ,听说好多,大厂大佬加班加点的改bug~😱

    然后,本人想好像本人对日志框架还不怎么了了解,只知道导入依赖,配置文件,正常的使用…更深入的就不太清除了...

    赶紧,学习总结了一波笔记~👍

日志框架📕

日志的概念

日志文件是用于记录系统操作事件的文件集合

  • 在计算机领域日志文件logfile

    是一个记录了发生在运行中的操作系统或其他软件中的事件的文件,或者记录了在网络聊天软件的用户之间发送的消息。

为什么需要日志:

  • 对于一个应用程序来说日志记录是必不可少的一部分

    可以帮助,开发者快速的定位问题,找到问题,进行解决…👍

现有的日志框架🛠

出现日期排序:

Java 日志框架发展了很多年,已经出现了很多个版本~发展过程也比较混乱~

  • log4j → JUL → JUCCommons Logging → Slf4j → Logback → log4j2

Java日志框架发展史✍:

log4j 1999

  • 它是 Apache 软件基金会的一个项目,在 jdk1.3 之前,还没有现成的日志框架:

    Java 工程师只能使用原始的 System.out.println() System.err.println() 或者 e.printStackTrace()

    把 debug 日志写到 StdOut 流,错误日志写到 ErrOut 流,以此记录应用程序的运行状态

  • 这种原始的日志记录方式缺陷明显,不仅无法实现定制化,而且日志的输出粒度不够细.

  • 1999 年,大牛 Ceki Gülcü 切基·居尔库 创建了 Log4j 项目

JUL 2002

  • Log4j 作为 Apache 基金会的一员,Apache 希望将 Log4j 引入 jdk 不过被 sun 公司拒绝了

  • 随后,sun 模仿 Log4j,在 jdk1.4 中引入了 JUL 🙃

    sun公司真傲娇,终于知道怎么破产的了~

JCL 2002

  • 因为,当时时面上出现了两种日志框架 不同的使用方式,不同日志级别~ 对开发者不是很友好~

  • 于是,2002年 阿帕奇 推出了 JCL日志门面 定义一套接口,具体实现由Log4j 或 JUL 来实现

    程序运行时会使用 ClassLoader类加载器 寻找和载入底层的日志库,因此可以自由选择由 log4j 或 JUL 来实现日志功能

  • 类似于JDBC

    定义了一组数据库的 增删改查接口, Mysql Oracle...数据库都实现类这些接口可以做到,无需更换代码切换数据库~

Slf4j 2005 & Logback 2006

  • 两个原因,JCL的实现方式太过麻烦,后期不方便扩展新的日志框架

    Ceki Gülcü 大佬,后面离开了阿帕奇,独自开发了一个新的 日志门面 Slf4j Logback是其的实现~

  • 它相较于 log4j 有更快的执行速度和更完善的功能大佬牛逼呀!🐂

log4j2 2014

  • 为了维护在 Java 日志江湖的地位

    防止 JCL、Log4j 被 Slf4j、Logback 组合取代 ,2014 年 Apache 推出了 Log4j2 来自老东家的'制裁🙃'

  • Log4j 2 与 log4j 不兼容不存在管来,经过大量深度优化,其性能显著提升👍

日志门面 与 日志框架:

经过上面,我们已经知道常用的日志框架有:Log4j JUL JCL Slf4j Logback Log4j2

这些日志框架可以分为两种类型:门面日志日志系统

日志门面:设计模式 外观模式

JCL、slf4j

  • 只提供日志相关的接口定义,即相应的 API 提供简单实现

  • 为了,方便不同的日志, 实现, 不会对代码进行大改动~提高开发者的使用~

日志系统:

JUL、logback、log4j、log4j2

  • 与日志门面相对,它提供了具体的日志接口实现,应用程序通过它执行日志打印的功能

JUL

JUL全称Java.util.Logging

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

JUL 架构介绍

img

Logger

  • 记录器,应用程序通过 getLogger(); 获取 Logger 对象,调用其 API 来发布日志信息
  • Logger 通常被认为是访问日志系统的入口程序

Handler

  • 处理器,每个 Logger 都会关联一个或者是一组 Handler,Console File

  • Logger 会将日志交给关联的 Handler 去做处理,由 Handler 负责将日志做记录.

  • Filter

    过滤器,根据需要定制哪些信息会被记录,哪些信息会被略过

  • Formatter

    格式化组件,它负责对日志中的数据和信息进行转换和格式化,所以它决定了我们输出日志最终的形式

  • Level

    日志的输出级别,每条日志消息都有一个关联的级别。根据输出级别的设置,用来展现最终所呈现的日志信息

日志记录器logger 有自己默认的,Filter Formatter Level,可以与一个 或 多个Hanlder关联进行日志输出~

入门Demo:

创建一个JUL Maven工程

JUL 是Java 本身提供的,所以,不需要引入任何依赖…

JULTest.Java

import org.junit.Test;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JULTest {
    //入门案例:
    @Test
    public void Test(){
        //1.获取日志记录器对象 Logger.getLogger("参数是一个唯一的字符串,一般来说使用类的'全路径名'");
        Logger logger = Logger.getLogger("com.wsm.JULTest");
        //2.日志记录输出
        logger.info("输出日志,info 级别~");

    /** 常用方法 */
        //通过 .log(); 方法,指定日志的输出级别
        logger.log(Level.INFO,"指定输出级别: info~");
        logger.log(Level.WARNING,"指定输出级别: WARNING~");

        //通过占位符形式,输出日志, 类似于 printf();
        String name = "WSM";
        Integer age = 3;
        logger.log(Level.INFO,"我叫: “{0},今年{1}岁",new Object[]{name,age});
    }
}

控制台输出:

十二月 15, 2021 9:43:06 下午 com.wsm.JULTest Test
信息: 输出日志,info 级别~
十二月 15, 2021 9:43:06 下午 com.wsm.JULTest Test
信息: 指定输出级别: info~
十二月 15, 2021 9:43:06 下午 com.wsm.JULTest Test
警告: 指定输出级别: WARNING~
十二月 15, 2021 9:43:06 下午 com.wsm.JULTest Test
信息: 我叫: “WSM,今年3岁

JUL 日志级别说明及展示

JUL 一个有七个日志级别,两个特殊的 off All

日志级别数值说明
OFFInteger.MAX_VALUE 最大整数关闭所有消息的日志记录
SEVERE中: 色歪啊~1000错误信息最高级的日志级别
WARNING900警告信息
INFO800默认信息默认级别
CONFIG700配置信息
FINE500详细信息(少)
FINER400详细信息(中)
FINEST300详细信息(多) 最低级的日志级别
ALLInteger.MIN_VALUE最小整数启用所有消息的日志记录

数值的意义在于: 设置指定了日志级别,最终展示的信息,必须大于> 指定级别的数值!

  • OFF 和 ALL 是特殊的日志级别 全部关闭 全部启动

高级Demo

验证,JUL默认输出级别:

//检查JUL默认输出级别
@Test
public void Test2(){
    // 1.获取日志记录器对象
    Logger logger = Logger.getLogger("com.itheima.JULTest");	//参数,保证唯一即可,一般填写 类路径名;
    // 2.日志记录输出
    logger.severe("severe");
    logger.warning("warning");
    logger.info("info"); 										//默认日志输出级别
    logger.config("config");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
}

//自定义日志级别1.0
@Test
public void Test3(){
    // 1.获取日志记录器对象
    Logger logger = Logger.getLogger("com.itheima.JULTest");    //参数,保证唯一即可,一般填写 类路径名;

    // 2.设置日志输出级别: 改变Logger 日志对象
    // 首先关闭系统默认配置
    logger.setUseParentHandlers(false);
    // 重新,配置日志具体级别 (你以为这样就行了吗? 执行发现啥也没有~ 因为,现在日志配置还不完整,缺少了 Handler 还有 Formatter )
    logger.setLevel(Level.ALL);

    // 3.日志记录输出
    logger.severe("severe");
    logger.warning("warning");
    logger.info("info"); 
    logger.config("config");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
}

//自定义日志级别2.0
@Test
public void Test4(){
    // 1.获取日志记录器对象
    Logger logger = Logger.getLogger("com.itheima.JULTest");
    // 2.设置日志输出级别: 改变Logger 日志对象
    // 首先关闭系统默认配置
    logger.setUseParentHandlers(false);
    // 重新,配置日志具体级别
    logger.setLevel(Level.ALL);
    // 创建ConsolHhandler 控制台输出
    ConsoleHandler consoleHandler = new ConsoleHandler();
    // 创建简单格式转换对象
    SimpleFormatter simpleFormatter = new SimpleFormatter();
    // 将Handler 和 Formatter进行关联: logger ——关联—— Handler ——关联—— Formatter
    consoleHandler.setFormatter(simpleFormatter);
    logger.addHandler(consoleHandler);
    // 设consoleHandler 控制台输出的级别~
    consoleHandler.setLevel(Level.ALL);

    // 3.日志记录输出
    logger.severe("severe");
    logger.warning("warning");
    logger.info("info"); 
    logger.config("config");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
}
Test2
十二月 15, 2021 9:45:17 下午 com.itheima.JULTest testLogLevel
严重: severe
十二月 15, 2021 9:45:17 下午 com.itheima.JULTest testLogLevel
警告: warning
十二月 15, 2021 9:45:17 下午 com.itheima.JULTest testLogLevel
信息: info

Test3
CMD输出空!因为,日志的配置不完整,缺少了 Handler Formatter

Test4
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
严重: severe
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
警告: warning
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
信息: info
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
配置: config
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
详细: fine
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
较详细: finer
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
非常详细: finest

Test2

  • 发现输出的日志之后三行… 其它的都没有输出出来… 因为,JUL的默认级别就是 info 小于info的级别数值都输出不出来

Test3

  • 没有一行数据,因为:日志的配置不完整,缺少了 Handler Formatter

Test4

  • 终于,完整的输出了所有 级别的日志

  • 创建了一个控制台的 ConsoleHandler 简单的输出格式 SimpleFormatter 并进行关联~🔗 最重要的是,设置了Console的输出级别,这个是你控制台输出的日志级别!

  • 注意:

    consoleHandler.setLevel(Level.ALL); 设置的级别 ,取绝于 logger.setLevel(Level.ALL);

    logger 是真正设置输入的日志级别,但它因为配置不完整需要,consoleHandler输出. consoleHandler的级别在大 logger级别放的小,输出的数据也不多~

    两者关系就像是两个水桶💧 logger小桶 放在consoleHandler大桶

    大桶的出水口在大,小桶一次出一滴水,大桶也只有一滴水流速🌊

    小桶出水口很大,每次流很多水,大桶的口小,每次流出的水也不大!

设置,日志文件输出:

//日志文件的输出:
@Test
public void Test5() throws IOException {
    // 1.获取日志记录器对象
    Logger logger = Logger.getLogger("com.itheima.JULTest");
    // 2.设置日志输出级别: 改变Logger 日志对象
    // 首先关闭系统默认配置
    logger.setUseParentHandlers(false);
    // 重新,配置日志具体级别
    logger.setLevel(Level.ALL);         //设置小桶流速!
    // 创建格式
    SimpleFormatter simpleFormatter = new SimpleFormatter();
    // 创建ConsolHhandler 控制台输出: 关联 输出级别
    ConsoleHandler consoleHandler = new ConsoleHandler();
    consoleHandler.setFormatter(simpleFormatter);
    logger.addHandler(consoleHandler);
    consoleHandler.setLevel(Level.ALL);

    // 创建文件输出 Handler
    FileHandler fileHandler = new FileHandler("C:\\Users\\王斯明\\Desktop\\jul.log");  /** 要确保输出的目录存在! */
    fileHandler.setFormatter(simpleFormatter);
    logger.addHandler(fileHandler);
    // 不设置,日志级别,输出日志~, 发现默认输出了全部, (继承了小桶logger的流量~)

    logger.severe("severe");
    logger.warning("warning");
    logger.info("info");
    logger.config("config");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
}
image-20211215232408799
  • 注意⚠:

    Logger 可以持有多个处理器 Handler

    FileHandler 来设置,日志的输出文件,地址目录要事先存在!

    如果不指定,输出日志级别,则默认,根据logger设置的级别Logger对象的父子关系~

JUL Logger父子关系:

// Logger对象父子关系
@Test
public void Test6(){
    Logger logger1 = Logger.getLogger("com.wsm");
    Logger logger2 = Logger.getLogger("com");

    // 测试
    System.out.println("logger1上级 与 logger2比较:"+(logger1.getParent() == logger2));
    // 所有日志记录器的顶级父元素 LogManager$RootLogger,name ""
    System.out.println("logger2 Parent:"+logger2.getParent() + ",name:" + logger2.getParent().getName());

    System.out.println("下级会默认继承上级的配置,改变logger2 logger1也会发生改变~");
    // 关闭默认配置
    logger2.setUseParentHandlers(false);
    // 设置logger2日志级别
    // 自定义配置日志级别
    // 创建ConsolHhandler 控制台输出
    ConsoleHandler consoleHandler = new ConsoleHandler();
    // 创建简单格式转换对象
    SimpleFormatter simpleFormatter = new SimpleFormatter();
    // 进行关联
    consoleHandler.setFormatter(simpleFormatter);
    logger2.addHandler(consoleHandler);
    // 配置日志具体级别
    logger2.setLevel(Level.ALL);
    consoleHandler.setLevel(Level.ALL);
    
	System.out.println();
    System.out.println("输出logger1 发现,输出级别变成了 info");
    logger1.severe("severe");
    logger1.warning("warning");
    logger1.info("info");
    logger1.config("config");
    logger1.fine("fine");
    logger1.finer("finer");
    logger1.finest("finest");
}
logger1上级 与 logger2比较:true
logger2 Parent:java.util.logging.LogManager$RootLogger@61e4705b,name:
下级会默认继承上级的配置,改变logger2 logger1也会发生改变~

输出logger1 发现,输出级别变成了 info
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
严重: severe
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
警告: warning
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
信息: info
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
配置: config
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
详细: fine
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
较详细: finer
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
非常详细: finest
  • JUL 的logger 对象具有父子关系

    下降会默认具有,上级的属性配置…`默认的上级是 LogManager$RootLogger@61e4705b,name

JUL 配置文件形式使用:

上面的,硬编码形式,可以完成简单的 日志记录输出,但是实际开发中,并不方便 开发中使用🙁

  • 上面我们知道,logger对象对象具有 父子关系 默认的上级是, LogManager$RootLogger@61e4705b,name
  • 那这个 LogManager$RootLogger@61e4705b,name 又是哪里来的,又为什么是默认 info 配置🧐?

源码分析

  • Logger.getLogger(""); 方法,获取一个日志记录器对象ctrl+右击 进入方法~

  • 发现只有一个 demandLogger(); 进入!,

    发现LogManager manager = LogManager.getLogManager(); 获取一个LogManager日志管理器对象!LogManager是一个单例对象,负责记录和管理日志对象

  • **进入 getLogManager(); 进入 ensureLogManagerInitialized(); 发现:owner.readPrimordialConfiguration();加载配置文件! **

    加载配置文件之后,就会执行 owner.rootLogger = owner.new RootLogger(); 设置顶级的,rootLogger 对象

  • 进入readPrimordialConfiguration(); 方法查看,加载配置文件流程:发现readConfiguration(); 读取配置文件,继续进入👉

    首先:String cname = System.getProperty("java.util.logging.config.class"); 当前系统是否存在一个配置类!没有则往下!

    判断 String fname = System.getProperty("java.util.logging.config.file"); 当然系统下 有没有 自定义的配置文件!

    如果没有则:fname = System.getProperty("java.home");获取Java安装目录,

    File f = new File(fname, "lib"); f = new File(f, "logging.properties"); 读取,JDK目录中的lib目录下的 logging.properties👍

  • image-20211216145550729

logging.properties

  • 本人移除了,官方的英文注释换成了,正常注释!
# 设置一个控制台输出的 Handler
handlers= java.util.logging.ConsoleHandler
# 设置默认的日志级别 Info
.level= INFO

# 配置默认的 文件处理器
# 指定日志文件默认的输出路径, (%h输出当前设备的用户目录) (%u输出日志的文件后缀1 2 3...)
java.util.logging.FileHandler.pattern = %h/java%u.log
# 当前文件最大存储多少条记录...
java.util.logging.FileHandler.limit = 50000
# 当前文件的数量,输出日志文件的个数~
java.util.logging.FileHandler.count = 1
# 当前 处理器输出文件的格式, 默认 xml格式输出!
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

# 控制台处理器的 默认级别INfo
java.util.logging.ConsoleHandler.level = INFO
# 日志输出的格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

# Example to customize the SimpleFormatter output format 
com.xyz.foo.level = SEVERE

因此,我们只需要,负责这个配置文件保证名字不变,到项目的 resources资源目录下即可通过配置文件形式,配置日志信息!

log配置文件形式进行操作:

resources资源目录下,添加 logging.properties

# RootLogger 顶级父元素指定的默认处理器为:ConsoleHandler,FileHandler (设置RootLogger 最高价关联的处理器)
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# RootLogger 顶级父元素默认的日志级别为:ALL
.level= ALL
# 关闭默认配置
com.itheima.useParentHanlders = false
        
# 向日志文件输出的 handler 对象
# 指定日志文件路径 /logs/java0.log  要确保安装目录存在!
java.util.logging.FileHandler.pattern = /java%u.log 
# 指定日志文件内容大小
java.util.logging.FileHandler.limit = 50000
# 指定日志文件数量
java.util.logging.FileHandler.count = 1
# 指定 handler 对象日志消息格式对象
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 指定以追加方式添加日志内容,(如果不添加,每一次添加的数据,都会替换之前的数据)
java.util.logging.FileHandler.append = true

# 向控制台输出的 handler 对象
# 指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = ALL
# 指定 handler 对象的日志消息格式对象
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定 handler 对象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
# 指定日志消息格式
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n

JULConfigTest.Java

/** 配置文件形式使用,JUL */
public class JULConfigTest {
    @Test
    public void Test() throws Exception{
        // 读取配置文件,通过类加载器
        InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
        // 创建LogManager
        LogManager logManager = LogManager.getLogManager();
        // 通过LogManager加载配置文件
        logManager.readConfiguration(ins);
        // 创建日志记录器
        Logger logger = Logger.getLogger("com.wsm");
        logger.info("--------------------------------------------------------------------------------------------");
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");

        System.out.println("");
//        // 创建日志记录器2
//        Logger logger2 = Logger.getLogger("www");
//        logger2.severe("severe");
//        logger2.warning("warning");
//        logger2.info("info");
//        logger2.config("config");
//        logger2.fine("fine");
//        logger2.finer("finer");
//        logger2.finest("finest");
    }
}

自定义配置:日志对象级别:

除了通过 logging.properties配置文件,设置大众的配置,也可以单独对于某一种的 日志对象进行设置!

## 自定义Logger使用,单独设置自定义的, 日志元素
www.handler = java.util.logging.ConsoleHandler
www.level = INFO

解除上面 Logger.getLogger("www"); 注释!

image-20211216164805173

ok, JUL就了解这么多了,现在用的也很少了…几乎没有公司在使用了...

Log4j

Log4j是Apache下的一款开源的日志框架:官方网站

  • 通过在Log4J,我们可以控制日志信息输出到:控制台文件甚至是数据库中
  • 我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以 更灵活的控制日志的输出过程

Log4j组件

Log4J 主要由:Loggers日志记录器 Appenders输出端 Layout日志格式化器

Loggers日志记录器

控制日志的输出级别与日志是否输出

  • Logger.getLogger(类的全限定名/类对象~);

  • Logger的名字大小写敏感,其命名有继承机制

    com.wsm 会继承,com限定名的日志属性~

  • Log4J中有一个特殊的logger叫做“root”

    他是所有logger的根,也就意味着其他所有的logger都会直接 或者间接地继承自rootroot logger可以用Logger.getRootLogger()方法获取

Appenders输出端

指定日志的输出地方输出到控制台、文件

输出端类型作用
ConsoleAppender将日志输出到控制台
FileAppender将日志输出到文件中
DailyRollingFileAppender将日志输出到一个日志文件,并且每天输出到一个新的文件
RollingFileAppender将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件
JDBCAppender把日志信息保存到数据库中

Layout日志格式化器

控制日志信息的输出格式

格式化器类型作用
HTMLLayout格式化日志输出为HTML表格形式
SimpleLayout简单的日志输出格式化,打印的日志格式为(info - message 输入什么打印什么.
PatternLayout最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式, 就是用默认的转换格式

Layout的格式:

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}
    %F 输出日志消息产生时所在的文件名称
    %L 输出代码中的行号
    %% 输出一个 "%" 字符
    
    %l 输出日志时间发生的位置,包括类名%c、线程%t、及在代码中的行数%L		如:Test.main(Test.java:10)
    
可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式. 如:
    %5c 	输出category名称,最小宽度是5,category<5,默认的情况下右对齐
    %-5c 	输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
    %.5c 	输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格
    %20.30c category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉

Log4j 入门Demo

建立maven工程

添加依赖 因为,不是Java本身提供的需要引入对应的依赖~

<!--log4j-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<!-- 为了方便测试,可以导入 JUnit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<!-- log4j 具有操作数据库的功能,引入对应的 数据库驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

Log4jTest.Java

//阿帕奇 log4j 的包哦~
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.junit.Test;

public class Log4jTest {
    /** 入门案例 */
    @Test
    public void test(){
        //1. 使用前需要初始化配置信息,不然会报错! (实际开发是通过,配置文件; 入门案例需要引入默认的)
        BasicConfigurator.configure();
        //2. 创建日志对象
        Logger logger = Logger.getLogger(Log4jTest.class);//不仅支持 唯一字符 还有 类对象

        //3. 输出日志信息!
        System.out.println("log4j 和 JUL 的日志级别,有些不同..");
        logger.fatal("fatal 严重错误,一般会造成系统崩溃并终止运行");
        logger.error("error 错误信息,不会影响系统运行");
        logger.warn("warn 警告信息,可能会发生问题");
        logger.info("info 追踪信息,记录程序所有的流程信息");
        logger.debug("debug 调试信息,一般在开发中使用,记录程序变量参数传递信息等等");
        logger.trace("trace 追踪信息,记录程序所有的流程信息");
    }
}

Log4j 配置文件使用:

源码分析:

  • Logger.getLogger() 进入,发现Logger 对象是又,一个LogManager对象.getLogger();创建

    进入LogManage类 发现 static{ 代码块中 } url = Loader.getResource("log4j.properties");

    类扫描读取log4j.properties 配置文件,只要 resource 资源目录下存在 log4j.properties文件,就不需要初始化配置信息了!

    static{ 代码块中 } 内部有一个OptionConverter.selectAndConfigure(..) 读取配置文件…根据文件类型,动态读取 xml properties 获取Configurator对象

  • Configurator对象用于加载,初始化 root 对象,可以通过它来,查看配置文件如何编写

    selectAndConfigure方法中, new PropertyConfigurator(); 进入

    发现:APPENDER_PREFIX RENDERER_PREFIX等很多的配置文件,属性…

rootLogger 配置文件加载默认 日志对象

log4j.properties resources 资源文件下,添加配置文件;⚙

# 设置默认的日志对象 rootLogger
# 日志级别 和 绑定的appender(可以多个)        逗号,分隔
log4j.rootLogger = trace,console
# 指定控制台日志输出的 appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定控制台日志输出的 layout 输出格式默认 SimpleLayout
log4j.appender.console.layout = org.apache.log4j.SimpleLayout

Log4jTest.Java

/** 配置文件加载: rootlogger对象;  */
@Test
public void test1(){
    //resources 目录下添加一个, log4j.properties
    //这样就不需要 BasicConfigurator.configure(); 加载默认配置文件了;
    
	// 开启 log4j 内置日志记录 (随便,开启日志输出,日志框架的日志信息~
    LogLog.setInternalDebugging(true);
   	
    //创建日志对象
    Logger logger = Logger.getLogger(Log4jTest.class);
    //输出日志信息!
    System.out.println("log4j 和 JUL 的日志级别,有些不同..");
    logger.fatal("fatal 严重错误,一般会造成系统崩溃并终止运行");
    logger.error("error 错误信息,不会影响系统运行");
    logger.warn("warn 警告信息,可能会发生问题");
    logger.info("info 追踪信息,记录程序所有的流程信息");
    logger.debug("debug 调试信息,一般在开发中使用,记录程序变量参数传递信息等等");
    logger.trace("trace 追踪信息,记录程序所有的流程信息");
}
log4j 和 JUL 的日志级别,有些不同..
FATAL - fatal 严重错误,一般会造成系统崩溃并终止运行
ERROR - error 错误信息,不会影响系统运行
WARN - warn 警告信息,可能会发生问题
INFO - info 追踪信息,记录程序所有的流程信息
DEBUG - debug 调试信息,一般在开发中使用,记录程序变量参数传递信息等等
TRACE - trace 追踪信息,记录程序所有的流程信息

尝试,修改配置文件,运行 test1()

# 指定控制台日志输出的 layout 输出格式默认 SimpleLayout (HTMLLayout(以HTML展示日志信息) / xml.XMLLayout(以xml形式展示数据) / PatternLayout(自定义常用!)
log4j.appender.console.layout = org.apache.log4j.HTMLLayout

输出的日志就会以,html 形式进行展示📺

PatternLayout 自定义,日志格式输出:

log4j.properties 追加

# 设置默认的日志对象 rootLogger
# 日志级别 和 绑定的appender(可以多个)        逗号,分隔
log4j.rootLogger = trace,console
# 指定控制台日志输出的 appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定控制台日志输出的 layout 输出格式默认 SimpleLayout (HTMLLayout / xml.XMLLayout / PatternLayout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容   layout.conversionPattern
log4j.appender.console.layout.conversionPattern = [%-10p]%r  %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
    # 设置,数据格式:
    # [%-10p]:      [数据长度10且居左] %r输出自应用启动到输出该log信息耗费的毫秒数
    # %r:           输出自应用启动到输出该 log 信息耗费的毫秒数
    # %l:           输出日志时间发生的位置,包括类名、线程、及在代码中的行数
    # %d:           输出服务器当前时间,默认为 ISO8601,也可以指定格式

后期这样的格式,公司都是有固定的规范的已经配置好了 了解即可!

输出日志,保存文件:file

log4j.properties 追加

设置 log4j.rootLogger = trace,console,file 绑定对应的 appender

# 日志级别 和 绑定的appender(可以多个)        逗号,分隔
log4j.rootLogger = trace,console,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
# 指定日志文件保存路径 (需要手动指定,日志文件保存到磁盘...不然出错,本人这回默认项目的盘符 D盘,没有文件创建文件~
log4j.appender.file.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.file.encoding = UTF-8

运行test1()

log4j 不像JUL ,数据默就追加了~

c6ae8f8d67c08c3253e8fa907ca99e3

按照文件大小拆分 rollingFile

log4j.properties 追加

设置 log4j.rootLogger = trace,console,rollingFile 绑定对应的 appender

# 日志级别 和 绑定的appender(可以多个)        逗号,分隔
log4j.rootLogger = trace,console,rollingFile

# 按照文件大小拆分的 appender 对象
# 日志文件输出的 appender 对象
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
# 指定消息格式 layout
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.rollingFile.layout.conversionPattern = [%-10p]%r  %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.rollingFile.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.rollingFile.encoding = UTF-8
# 指定日志文件内容的大小( 没个文件1mb 产生新的日志文件~
log4j.appender.rollingFile.maxFileSize = 1MB 
# 指定日志文件的数量(最多产生 10个日志文件, log4j.log log4j2.log log4j3.log, 超出文件个数,旧的文件会新文件被覆盖
log4j.appender.rollingFile.maxBackupIndex = 10

Log4jTest.Java 循环1w次查看结果~

for (int i = 0; i < 10000; i++) {
	logger.fatal("fatal 严重错误,一般会造成系统崩溃并终止运行");
	logger.error("error 错误信息,不会影响系统运行");
	logger.warn("warn 警告信息,可能会发生问题");
	logger.info("info 追踪信息,记录程序所有的流程信息");
	logger.debug("debug 调试信息,一般在开发中使用,记录程序变量参数传递信息等等");
	logger.trace("trace 追踪信息,记录程序所有的流程信息");
}

image-20211219215721657

按照时间规则拆分的 dailyFile

log4j.properties 追加

设置 log4j.rootLogger = trace,console,dailyFile 绑定对应的 appender

# 日志级别 和 绑定的appender(可以多个)        逗号,分隔
log4j.rootLogger = trace,console,dailyFile

# 按照时间规则拆分的 appender 对象;(默认每一天,生成一个新的文件~
log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
# 指定消息格式 layout
log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.dailyFile.layout.conversionPattern = [%-10p]%r  %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.dailyFile.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.dailyFile.encoding = UTF-8
# 指定日期拆分规则 (年月日 时分秒,每秒生成一个文件~
log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss

运行:test1()

image-20211219220024508

日志文件 写入数据库:

创建对应的数据库, 数据库表脚本 .sql

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`)
);

log4j.properties 追加

设置 log4j.rootLogger = trace,console,dailyFile 绑定对应的 appender

# 日志级别 和 绑定的appender(可以多个)        逗号,分隔
log4j.rootLogger = trace,console,logDB

#mysql
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.jdbc.Driver
# 设置数据库: 连接信息还有操作的数据库,(本人的数据库的 log~
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/log
# 数据库 用户名
log4j.appender.logDB.User=root
# 数据库 密码
log4j.appender.logDB.Password=ok
# 设置入库的语句, %? 对应列要存的数据,参考,PatternLayout
log4j.appender.logDB.Sql=INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')

运行test1()

image-20211219222610641

自定义 logger 对象:

作用:对于多个生成环境,需要记录的是不同的日志…可以使用 自定义日志对象类设置~

log4j.properties

# 自定义 logger 对象设置 (输出日志级别更改为了 info, appender还会默认继承 rootlogger
log4j.logger.com.wsm = info,console

Log4jTest.Java

/** 自定义 logger 对象 */@Testpublic void test2(){    //获取自定义日志对象~    Logger logger = Logger.getLogger("com.wsm");    //输出~    logger.fatal("fatal 严重错误,一般会造成系统崩溃并终止运行");    logger.error("error 错误信息,不会影响系统运行");    logger.warn("warn 警告信息,可能会发生问题");    logger.info("info 追踪信息,记录程序所有的流程信息");    logger.debug("debug 调试信息,一般在开发中使用,记录程序变量参数传递信息等等");    logger.trace("trace 追踪信息,记录程序所有的流程信息");}

只展示大于等于 info 的日志级别~

[FATAL     ]0  com.wsm.Log4jTest.test2(Log4jTest.java:57) 2021-12-19 22:35:11.558 fatal 严重错误,一般会造成系统崩溃并终止运行
[ERROR     ]3  com.wsm.Log4jTest.test2(Log4jTest.java:58) 2021-12-19 22:35:11.561 error 错误信息,不会影响系统运行
[WARN      ]3  com.wsm.Log4jTest.test2(Log4jTest.java:59) 2021-12-19 22:35:11.561 warn 警告信息,可能会发生问题
[INFO      ]3  com.wsm.Log4jTest.test2(Log4jTest.java:60) 2021-12-19 22:35:11.561 info 追踪信息,记录程序所有的流程信息

JCL

全称为Jakarta Commons Logging,是Apache提供的一个通用日志API,是一种日志门面

  • 它为 “所有的Java日志实现"提供一个统一的接口”,它自身也提供一个日志的实现,但是功能非常常弱 SimpleLog几乎被淘汰了, 一般不会单独使用它

    他允许开发人员使用不同的具体日志实现工具: Log4j, Jdk 自带的日志(JUL)

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

JCL入门

创建Maven工程

添加 pom.xml依赖

<dependencies>
    <!-- JCL的日志依赖 -->
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <!-- junit的依赖 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!-- log4j的依赖! -->
    <!--         <dependency>-->
    <!--             <groupId>log4j</groupId>-->
    <!--             <artifactId>log4j</artifactId>-->
    <!--             <version>1.2.17</version>-->
    <!--         </dependency>-->
</dependencies>

JCLTest.Java

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
/** JUL入门案例~ */
public class JCLTest {
    @Test
    public void test(){
        // 获取 log日志记录器对象, JCL通过一个日志工厂 获取一个日志对象;
        Log log = LogFactory.getLog(com.wsm.JCLTest.class);
        // 日志记录输出
        log.info("hello jcl");
    }
}

输出了和 JUL一样的日志数据, 当没有引入 log4j依赖时候 JCL默认就通过JUL实现,则使用log4j

十二月 19, 2021 11:02:59 下午 com.wsm.JCLTest test
信息: hello jcl

切换 log4j日志框架

解开log4j的依赖配置 添加:依赖,配置文件 log4j.properties

# 设置默认的日志对象 rootLogger
# 日志级别 和 绑定的appender(可以多个)        逗号,分隔
log4j.rootLogger = trace,console
# 指定控制台日志输出的 appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定控制台日志输出的 layout 输出格式默认 SimpleLayout (HTMLLayout / xml.XMLLayout / PatternLayout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容   layout.conversionPattern
log4j.appender.console.layout.conversionPattern = [%-10p]%r  %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n

运行test(),是log4j的输出方式

[INFO      ]1  com.wsm.JCLTest.test(JCLTest.java:13) 2021-12-19 23:17:35.636 hello jcl

总结:⭐

JCL 是一个日志门面,随着日志的使用频繁市面上又有很多个不同的日志框架,开发者需要同时掌握多个日志框架(实在不是很友好~

阿帕奇,就退出了一个日志门面,来统一实现了接口,JUL 和 log4j都对其进行了实现。

好处,开发者只需要学习一个日志框架的使用,即使后面更改日志框架,也不会对代码产生任何影响~👍 只需要替换一个依赖即可!

  • JCL 是由默认实现的SimpleLog,但很少有人使用~

  • JUL 是Java自带的,所以不需要引入依赖,就默认使用了JUL实现!

但,JCL 对于不同日志框架的切换实现…

是以一种,硬编码形式实现,之兼容了少量的日志框架,如果后面需要引入新的日志,还需要更改配置文件~

所以,后面又出现了一种新的日志门面 Slf4j 它可以兼容目前市面上所有/以及未来的 日志框架

Slf4j

简单日志门面Simple Logging Facade For Java

  • SLF4J主要是为了给Java日志访问提供一套标准、规范 的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架

  • 当然slf4j自己也提供了功能较为简单的实现但是一般很少用到

  • 现在很多的Java项目都使用,slf4j-api作为门面

  • 配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接

  • 官方地址

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

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

SLF4J入门

创建Maven工程:

导入对应的依赖:

<!-- slf4j 日志门面 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.26</version>
</dependency>

Slf4jTest.Java

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Slf4j入门案例 */
public class Slf4jTest {
    //声明全局的 日志对象
    public final static Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
    /** 简单入门 */
    @Test
    public void Test(){
        // 日志输出(默认输出至,控制台 info级别;
        LOGGER.error("error");
        LOGGER.warn("wring");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");

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

        // 将系统的异常信息输出
        try {
            int i = 1/0;
        } catch (Exception e) {
            // e.printStackTrace();  这样程序就不会,打印Java的 堆栈一推错误了...
            LOGGER.error("出现异常:",e);
        }
    }
}
[main] ERROR com.wsm.Slf4jTest - error
[main] WARN com.wsm.Slf4jTest - wring
[main] INFO com.wsm.Slf4jTest - info
[main] INFO com.wsm.Slf4jTest - 用户:wsm,540
[main] ERROR com.wsm.Slf4jTest - 出现异常:
java.lang.ArithmeticException: / by zero
	at com.wsm.Slf4jTest.Test(Slf4jTest.java:29) 
#省略下面更多,异常信息~	

日志框架的绑定

img

Slf4j 作为一个强大的日志门面,可以在不改变代码的情况下,实现切换不同的日志框架…支持不同的日志框架的绑定!

上图,是Slf4j 官方的图片,与各个框架进行绑定的介绍:

application 指定是应用程序 SLF4J API 就是Slf4j日志门面 从左往右——>1-6

  • Slf4j无绑定项目只是单纯的引入了 日志门面...

  • ② ⑤ ⑥ 分别是 logback slf4j内置实现 log4j2

    都是在,slf4j门面之后出现的,因此都对其进行了实现,之间导入 Slf4j日志门面依赖对应实现依赖即可,自动绑定完成

  • ③ ④ 是在 slf4j之前开发的,没有直接的实现 slf4j门面

    所以在,使用之前还需要导入对应的 Adaptation适配器来进适配.

    使用 slf4j门面方法,内部适配器在调用,JUL / log4j 的方法~

接下来,粗略介绍一下各个日志框架的绑定:slf4j内置实现,入门案例就是

Logback 绑定

因为,已经默认实现类,slf4j 直接引入依赖即可… 别忘记注释,slf4j内置实现依赖

  • 这里不详细,介绍Logback使用.
<!--logback 日志实现-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

slf4j 空实现:

当我们还不能确定,使用什么日志框架来实现时候,可以引入 slf4j-nop

它是 slf4j的一种空实现,因为如果不引入 slf4j的实现,会报错Defaulting to no-operation (NOP) logger implementation

  • 注释其它依赖 日志实习依赖 运行程序异常~
  • 添加 slf4j-nop 依赖,运行test();
<!-- nop 日志开关: slf4j的一种空实现~ -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.25</version>
</dependency>
  • 运行,不出异常,但无结果…

Log4j 绑定

因为,Log4j诞生早于,Slf4j所以并没有直接对其进行实现,slf4j 与 log4j 进行绑定还需要添加一个 适配器

注释其它实现依赖…

<!--绑定 log4j 日志实现,需要导入适配器 --><dependency>    <groupId>org.slf4j</groupId>    <artifactId>slf4j-log4j12</artifactId>    <version>1.7.12</version></dependency><!-- log4j依赖 --><dependency>    <groupId>log4j</groupId>    <artifactId>log4j</artifactId>    <version>1.2.17</version></dependency>

运行 test()报错:log4j:WARN No appenders could be found for logger (com.wsm.Slf4jTest).

  • 需要引入对应的 appenders 需要,添加对应的log4j.properties配置文件~

JUL 绑定

同上,JUL也需要添加一个适配器 因为JUL 是JDk 自带的,所以,只需要引入一个 适配器依赖即可!

<!--绑定 jul 日志实现,需要导入适配器 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.25</version>
</dependency>

JUL ,JDK内有默认的 配置文件.

总结:⭐

使用slf4j的日志绑定流程:

  • 添加slf4j-api的依赖

  • 使用slf4j的API在项目中进行统一的日志记录

  • 绑定具体的日志实现框架:

    绑定已经实现了slf4j的日志框架,直接添加对应依赖

    绑定没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖

  • slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现

日志框架的桥接Bridging

img

假设,突然让你维护一个比较老的项目。老板让你把原先项目的日志框架 log4j 换成 logback

  • 咋办,两个框架一个属于 slf4j门面 一个属于 JCL门面 直接替换jar包,肯定不行!
  • 那怎么办❓ 移除依赖,修改代码❓ 那真的太狗血了🐕!

还好,slf4j 提供了桥接,技术 支持开发者引入对应的日志的 桥接器 来完成配置!

log4j 切换 logback Demo

Slf4j模块,添加 Log4jTest.Java

import org.apache.log4j.Logger;
import org.junit.Test;

public class Log4jTest {
    // 定义 log4j 日志记录器
    public static final Logger LOGGER = Logger.getLogger(Log4jTest.class);
    // 测试桥接器
    @Test
    public void test01()throws Exception{
        LOGGER.info("hello lgo4j");
    }
}

别忘记了 log4j 需要引入配置文件log4j.properties, 运行 test01();

15:52:04.308 [main] INFO com.itheima.Log4jTest - hello lgo4j

上面是 log4j的代码和输出,下面是,切换的logback的代码输出..

引入依赖:注释log4j的依赖...

<!-- Slf4j的桥接技术 -->
<!-- 将log4j 日志框架,不更改代码清空下,替换为 logback: 注释掉所有的依赖,只保留 JUnit -->

<!-- log4j的所需依赖 -->
<!--        <dependency>-->
<!--            <groupId>log4j</groupId>-->
<!--            <artifactId>log4j</artifactId>-->
<!--            <version>1.2.17</version>-->
<!--        </dependency>-->

<!-- 注释,log4j 的实现依赖; -->
    <!--logback 日志实现-->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>

    <!--配置 log4j 的桥接器-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>log4j-over-slf4j</artifactId>
        <version>1.7.25</version>
    </dependency>
十二月 20, 2021 4:48:50 下午 com.wsm.log4jTest test01
信息: hello lgo4j

发现代码不变,输出的方式已经变成了 logback

原因:log4j-over-slf4j桥接器,内部调用了 Slf4j的方法,相当于进行了一个重定向~

注意⚠:

log4j-over-slf4j.jarslf4j-log4j12.jar不能同时出现

jcl-over-slf4j.jarslf4j-jcl.jar不能同时出现

jul-to-slf4j.jarslf4j-jdk14.jar不能同时出现

日志桥接器,不能和 日志的实现依赖同时出现,因为,日志桥接器 底层还是去调用Slf4j的接口方法. Slf4j 回去调用日志实现

而如果同时引入了,相同的 桥接器 日志实现,日志实现 --调用–> 桥接器 --调用–> Slf4j --调用–> 日志实现 陷入死循环,堆栈溢出!

Logback

Logback是由log4j创始人设计的另一个开源日志组件:官方地址

Logback主要分为三个模块:

  • logback-core:其它两个模块的基础模块, Maven具有继承特性,引入了 classic/access 就默认具有 core了
  • logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API
  • logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能 用于Tomcat/Jetty 日志直接输出到对应的服务器日志中去!

Logback 入门案例:

创建Maven 工程

引入 logback 依赖:

<!--slf4j 日志门面-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.26</version>
</dependency>
<!--logback 日志实现-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<!--junti 单元测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

LogbackTest.Java

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** logBack日志入门案例 */
public class LogbackTest {
    //创建全局的 logger 对象
    public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);
    @Test
    public void test(){
        //调用 Slf4j 接口实现日志输出;
        // logback 一共有五个日志级别,的默认级别是, debug
        LOGGER.error("error");
        LOGGER.warn("wring");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}

存在,默认的配置 rootLogger 默认:控制台输出, 日志级别debug 😶

10:28:18.657 [main] ERROR com.wsm.LogbackTest - error
10:28:18.662 [main] WARN com.wsm.LogbackTest - wring
10:28:18.662 [main] INFO com.wsm.LogbackTest - info
10:28:18.662 [main] DEBUG com.wsm.LogbackTest - debug

Logback 介绍:

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

  • logback.groovy
  • logback-test.xml
  • logback.xml 如果均不存在会采用默认配置 在 resources 资源目录下,创建任意一个配置文件即可

xml 格式,方便读取,和操作, 使用也相对比较多,本篇介绍logback.xml 配置文件编写✍~

logback组件之间的关系:

和其它日志框架,也大致一样,日志对象 记录器 日志输出格式

Logger 日志记录器对象

  • 根据context环境(配置文件LoggerFactory.getLogger()生成一个 日志记录器对象,主要用于存放日志对象,定义日志类型、级别

  • Logger 可以被分配级别:

    **级别包括:**TRACE、DEBUG、INFO、WARN 和 ERROR 定义于ch.qos.logback.classic.Level

Appender 记录器

  • 用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等

Layout 日志输出格式

  • 负责把事件转换成字符串,格式化的日志信息的输出,logback中Layout对象被封 装在encoder中

Logback 配置⚙:

配置文件入门:

resources 资源目录下添加: logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- configuration xml的根节点,存在三个属性;
        scan:       当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true
        scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒,当scan为true时,此属性生效;
        debug:      当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false;
    -->
<configuration scan="true" scanPeriod="60 seconds" debug="false"> 
	<!-- contextName:
            用来设置上下文名称,每个logger都关联到logger上下文,默认上下文名称为default;
            置成其他名字,用于区分不同应用程序的记录;
    -->
    <contextName>mylog</contextName>

    <!-- property:
            用来定义变量值,它有两个属性name和value,通过<property>定义的值 可以被配置文件上下文引用, ${name名} 来使用变量
            name: 变量的名称
            value: 的值时变量定义的值
    -->
    <!--定义日志文件保存路径属性-->
    <property name="log_dir" value="/logs"></property>
    <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property>
    <!-- 日志输出格式:
            %-5level
            %d{yyyy-MM-dd HH:mm:ss.SSS}日期
            %c类的完整名称
            %M为method
            %L为行号
            %thread线程名称
            %m或者%msg为信息
            %n换行  -->
</configuration> 

控制台日志输出的 appender

logback.xml追加:

<!-- 控制台日志输出的 appender 它有两个必要属性name和class;
        name指定appender名称
        class指定appender的全限定名,不同的 输出Appender 又有不同的子元素设置:
-->
<!-- ConsoleAppender 把日志输出到控制台;
        <encoder>:  对日志进行格式化
        <target>:   Java控制台输出,字符串System.out(默认)或者System.err(红色字体
-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <!--控制输出流对象 默认 System.out 改为 System.err-->
    <target>System.err</target>
    <!--日志消息格式配置-->
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <pattern>${pattern}</pattern>
    </encoder>
</appender>

<!-- rootlogger 配置:
        level:          设置日志输出级别;
        <appender-ref>: 设置rootlogger 绑定的appender,支持绑定多个~
 -->
<root level="ALL">
    <appender-ref ref="console"/>	<!-- 根据 appender 的name 属性进行关联; -->
</root>

控制台:输出对应格式的 system.err 输出的红色字体~

[ERROR] 2021-12-21 16:54:09.008 com.itheima.LogbackTest testQuick 16 [main] error
[WARN ] 2021-12-21 16:54:09.012 com.itheima.LogbackTest testQuick 17 [main] wring
[INFO ] 2021-12-21 16:54:09.012 com.itheima.LogbackTest testQuick 18 [main] info
[DEBUG] 2021-12-21 16:54:09.012 com.itheima.LogbackTest testQuick 19 [main] debug
[TRACE] 2021-12-21 16:54:09.012 com.itheima.LogbackTest testQuick 20 [main] trace

输出日志文件:

logback.xml

<!-- 日志文件输出的 appender-->
<!-- FileAppender 把日志添加到文件;
        <file>:     被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值;
        <append>:   如果是 true 日志被追加到文件结尾 (默认 true;
        <encoder>:  对记录事件进行格式化;
        <prudent>:  如果是 true,日志会被安全的写入文件,即使其他的FileAppender也在向此文件做写入操作; (效率低默认是 false;
 -->
<appender name="file" class="ch.qos.logback.core.FileAppender">
    <!--日志文件保存路径: ${log_dir}引入上面的,全局变量-->
    <file>${log_dir}/logback.log</file>
    <!--日志消息格式配置-->
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <pattern>${pattern}</pattern>
    </encoder>
</appender>

<!-- rootlogger 配置:
        level:          设置日志输出级别;
        <appender-ref>: 设置rootlogger 绑定的appender,支持绑定多个~
 -->
<root level="ALL">
    <appender-ref ref="console"/>	<!-- 根据 appender 的name 属性进行关联; -->
    <appender-ref ref="file"/>
</root>

image-20211221173941677

输出文件 html

logback.xml

<!--html 格式日志文件输出 appender-->
<appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
    <!--日志文件保存路径-->
    <file>${log_dir}/logback.html</file>
    <!--html 消息格式配置-->
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="ch.qos.logback.classic.html.HTMLLayout">
            <pattern>%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m</pattern>
        </layout>
    </encoder>
</appender>

<!-- 设置:rootlogger 配置 -->

image-20211221174645218

日志拆分和归档压缩 / 异步日志

logback.xml

<!-- 日志拆分和归档压缩的 appender 对象-->
<!-- RollingFileAppender
        省略与 FileAppender 相同的元素;
        <rollingPolicy>:    当发生滚动时,决定RollingFileAppender的行为,涉及文件移动和重命名。属性class定义具体的滚动策略类;
        <prudent>:          当为true时,不支持FixedWindowRollingPolicy,
 -->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!--日志文件保存路径-->
    <file>${log_dir}/roll_logback.log</file>
    <!--日志消息格式配置-->
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <pattern>${pattern}</pattern>
    </encoder>
    <!--指定拆分规则-->
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <!--fileNamePattern 按照时间和压缩格式声明拆分的文件名-->
        <fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
        <!--maxFileSize 设置按照文件大小拆分: -->
        <maxFileSize>1MB</maxFileSize>
        <!-- maxHistory 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件; -->
    </rollingPolicy>

    <!--日志级别过滤器-->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <!--日志过滤规则
               <level>:         设置过滤级别
               <onMatch>:       用于配置符合过滤条件的操作
               <onMismatch>:    用于配置不符合过滤条件的操作

            onMatch/onMismatch  执行一个过滤器会有返回个枚举值: DENY,NEUTRAL,ACCEPT
                DENY            日志将立即被抛弃不再经过其他过滤器;
                NEUTRAL         有序列表里的下个过滤器过接着处理日志;
                ACCEPT          日志会被立即处理,不再经过剩余过滤器;
         -->
        <level>ERROR</level>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
</appender>


<!-- 异步日志
       appender支持通过, appender-ref 直接引用其它的 appender属性~
 -->
<!-- AsyncAppender
        写出文件,时候会开启一个线程,以异步形式写出日志文件~
 -->
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
    <!--指定某个具体的 appender-->
    <appender-ref ref="rollFile"/>
</appender>

<!-- 设置:rootlogger 配置 -->

为了能够看到效果, 循环 1w次日志打印~

77bdfb3500ed3ec56f2d60abb9ce519

自定义 looger 对象

logback.xml

<!--自定义 looger 对象
    additivity="false" 自定义 logger 对象是否继承 rootLogger
 -->
<logger name="com.wsm" level="info" additivity="false">
    <appender-ref ref="console"/>
</logger>

name 设置项目的 包名.包名.xxx 该包下,会默认具有 自定义logger对象, additivity可以用于设置是否继承 rootlogger

d627d928d374c838062a5e4371d93d5

[ERROR] 2021-12-21 23:20:13.702 com.wsm.LogbackTest test 16 [main] error
[WARN ] 2021-12-21 23:20:13.705 com.wsm.LogbackTest test 17 [main] wring
[INFO ] 2021-12-21 23:20:13.705 com.wsm.LogbackTest test 18 [main] info

输出级别变成了 info

Log4j2:

Apache Log4j 2是对Log4j的升级版 [官方地址](Log4j – Apache Log4j 2)

参考了logback的一些优秀的设计,并且修复了一些问题,因此带 来了一些重大的提升:

  • 异常处理

    在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异 常处理机制;

  • 性能提升

    log4j2相较于log4j 和logback都具有很明显的性能提升 听说提供了十几倍!

  • 自动重载配置

    参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产 上可以动态的修改日志的级别而不需要重启应用;

  • 无垃圾机制

    log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集,导致的jvm gc;

Log4j2入门案例:

创建一个 Maven 工程:

引入依赖:pom.xml

<dependencies>
    <!--log4j2日志门面-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.11.1</version>
    </dependency>
    <!--log4j2 日志实现-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.11.1</version>
    </dependency>
    <!--junit 单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

编写入门案例:Log4j2Test.Java

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

/** log4j2 快速入门 */
public class Log4j2Test {
    // 定义日志记录器对象 LogManager.getLogger();
    public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
    /** 快速入门 */
    @Test
    public void test(){
        //log4j2 默认日志级别是 error;
        LOGGER.fatal("fatal");
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("inf");
        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
09:38:36.825 [main] FATAL com.wsm.Log4j2Test - fatal
09:38:36.838 [main] ERROR com.wsm.Log4j2Test - error

输出结果,提示没有配置对应的 配置文件!,可以在 resources资源目录下创建一个 log4j2.xml的配置文件 log4j2 的配置文件 和 logback大致相同;

虽然输出结果,有警告信息,但是依然正常的打印了日志:log4j2的默认日志级别是 error

Log4j2 配置文件:

当前 resources 资源目录下创建一个 log4j2.xml 与 logback.xml配置文件大致相同~

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
    status="warn" 日志框架本身的输出日志级别
    monitorInterval="5" 自动加载配置文件的间隔时间,不低于 5 秒,好处是:生产环境下的项目,进行更改,系统会自动重新加载!
-->
<Configuration status="debug" monitorInterval="5">

    <!-- 集中配置属性进行管理,想到于: 成员变量 ,使用时通过:${name} -->
    <properties>
        <property name="LOG_HOME">/logs</property>
    </properties>

    <!--日志处理,设置appenders 集-->
    <Appenders>
        <!--控制台输出 appender
				target:	控制台输出的方式 System.err / System.out	
		-->
        <Console name="Console" target="SYSTEM_ERR">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
        </Console>

        <!--日志文件输出 appender-->
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </File>
        
        <!--使用随机读写流的日志文件输出 appender,性能提高-->
        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </RandomAccessFile>

        <!--按照一定规则拆分的日志文件的 appender
				filePattern:	拆分文件名,$${date:yyyy-MM-dd}每天生成一个文件夹, %i 数字排序~
		-->
        <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log" filePattern="/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
            <!--日志级别过滤器-->
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
            <!--日志消息格式-->
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
            <Policies>
                <!--在系统启动时,出发拆分规则,生产一个新的日志文件-->
                <OnStartupTriggeringPolicy />
                <!--按照文件大小拆分,10MB -->
                <SizeBasedTriggeringPolicy size="10 MB" />
                <!--按照时间节点拆分,规则根据filePattern定义的-->
                <TimeBasedTriggeringPolicy />
            </Policies>
            <!--在同一个目录下,文件的个数限定为 30 个,超过进行覆盖-->
            <DefaultRolloverStrategy max="30" />
        </RollingFile>
    </Appenders>

    <!--logger 定义-->
    <Loggers>
        <!--使用 rootLogger 配置 日志级别 level="trace"-->
        <Root level="trace">
            <!--指定日志使用的处理器-->
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>
15:41:00.743 [main] [FATAL] com.wsm.Log4j2Test:15 --- fatal
15:41:00.754 [main] [ERROR] com.wsm.Log4j2Test:16 --- error
15:41:00.754 [main] [WARN ] com.wsm.Log4j2Test:17 --- warn
15:41:00.755 [main] [INFO ] com.wsm.Log4j2Test:18 --- inf
15:41:00.755 [main] [DEBUG] com.wsm.Log4j2Test:19 --- debug
15:41:00.755 [main] [TRACE] com.wsm.Log4j2Test:20 --- trace

直接输出就没有,异常信息了, 而且日志级别是 trace

Slf4j + log4j2

目前市面上最主流的日志门面就是SLF4J,Log4j2也是日志门面 也有自己对应的实现,现功能非常强大,性能优越

  • 虽然,Slf4j 和 logback 是同一个开发者,但是log4j2 功能强大!
  • Slf4j + Log4j2应该是未来的大势所趋 可最近出的一个bug 估计,也危险了☠

Slf4j + log4j2 整合案例:

pom.xml 添加依赖:

  • 使用 slf4j 作为 日志门面,需要使用 log4j2的适配器:
<!--使用slf4j 作为日志门面-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.26</version>
</dependency>

<!--使用 log4j2 的适配器进行绑定-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.9.1</version>
</dependency>

重新编写 slf4j 的接口调用方法! Slf4jTest.Java

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** slf4j + log4j2整合 */
public class Slf4jTest {
    //slf4j 的获取,日志对象的方法;
    public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);

    // 快速入门
    @Test
    public void test01()throws Exception{
        // 日志输出
        LOGGER.error("error");
        LOGGER.warn("wring");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}
15:52:17.495 [main] [ERROR] com.wsm.Slf4jTest:16 --- error
15:52:17.500 [main] [WARN ] com.wsm.Slf4jTest:17 --- wring
15:52:17.501 [main] [INFO ] com.wsm.Slf4jTest:18 --- info
15:52:17.501 [main] [DEBUG] com.wsm.Slf4jTest:19 --- debug
15:52:17.501 [main] [TRACE] com.wsm.Slf4jTest:20 --- trace

现在输出的就是 Slf4j 的,数据了,但是它底层仍然是 log4j2 所以 log4j2.xml配置文件仍然有效果😀

异步日志:

log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益 大大的提高了程序运行效率;

Log4j2提供了两种实现异步日志的方式:

  • 一个是通过AsyncAppender 效率并没有特别大提升
  • 一个是通过AsyncLogger 常用

两者都需要添加 依赖:

<!--异步日志依赖-->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.3.4</version>
</dependency>

AsyncAppender 异步记录器;

AsyncAppender 方式就直接在 log4j2.xml 添加对应的适配;

 <Appenders>       
     <!-- 异步输出 appender -->
     <Async name="Async">
         <!-- 指定需要被异步执行的适配器: ref="适配器name" -->
         <AppenderRef ref="Console"/>
         <!-- <AppenderRef ref="file"/> 可以多个...-->
     </Async>
 </Appenders>

AsyncLogger 异步对象;

AsyncLogger 方式, 存在两种方式 全局异步和混合异步

  • 全局异步

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

  • 混合异步

    你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活

全局异步对象:

只需要在 resources 资源目录下,创建一个配置文件 log4j2.component.properties 即可:

Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
混合异步对象:

就是在 log4j2.xml 文件,< Loggers > 中定义,对应的包路径下类对象… 设置成异步执行

<!--logger 定义-->
<Loggers>
    <!-- 自定义异步 logger 对象 -->
    <AsyncLogger name="com.wsm" level="trace" includeLocation="false" additivity="false">
        <AppenderRef ref="Console"/>
    </AsyncLogger>
</Loggers>

无垃圾模式🚮:

垃圾收集暂停是延迟峰值的常见原因,并且对于许多系统而言,花费大量精力来控制这些暂停

  • 许多的日志框架, 在稳态日志记录期间分配临时对象

    日志事件对象,字符串, 字符数组,字节数组等。这会对垃圾收集器造成压力并增加GC暂停发生的频率;

  • Log4j2 在2.6 版本后, 默认引入了 无垃圾运行模式 尽量的,不使用临时对象

  • 2.6之后就默认使用了 无垃圾模式

    log4j2.enableThreadlocals 设置为 true, 对象存储在 ThreadLocal字段中并重新使用,否则将为每个日志事件创建新对象 非Web应用程序的默认值

    log4j2.enableDirectEncoders 设置为 true 日志事件转换为文本,则将此文本转换 为字节而不创建临时对象 注意: 由于使用共享缓冲区上的同步 所以建议使用异步记录器!

    所以,只要升级到2.6 版本即可~

总结:

log4j2 日志框架性能,当于其它框架高很多,主要在 2.6之后引入了两个概念: 异步日志 无垃圾模式

SpringBoot 整合日志框架:

Idea Spring lnitializr Spring构造器 创建一个 SpringBoot 工程:

c608464e868bc3897adc2866ec1f7cc

SpringBoot 底层默认使用:SLF4J作为日志门面 logback作为日志实现

Maven依赖关系图:

bc535d9722887bcd3e52450dbe0660e

也引入了JUL 和 log4j 的桥接, 可以直接转换!

SpringBoot日志使用

test 模块下:DemoApplicationTests.Java

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {
    //创建日志记录器   导报: slf4j
    public static final Logger LOGGER =  LoggerFactory.getLogger(DemoApplicationTests.class);
    @Test
    void contextLoads() {
        // 打印日志信息
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}

省略… 以上SpringBoot 日志

2021-12-23 15:12:08.771 ERROR 18772 --- [           main] com.wsm.demo.DemoApplicationTests        : error
2021-12-23 15:12:08.771  WARN 18772 --- [           main] com.wsm.demo.DemoApplicationTests        : warn
2021-12-23 15:12:08.771  INFO 18772 --- [           main] com.wsm.demo.DemoApplicationTests        : info

可以看到,SpringBoot 默认集成了 Slf4j logback 默认日志级别是 info 还可以通过,SpringBoot application.properties/yml 配置进行配置文件的更改!

SpringBoot 配置文件 ⚙

SpringBoot 可以通过,配置文件,进行简单的修改日志配置:

application.properties 这里使用 pro 可以自行替换为 yml

# 指定自定义 logger 对象日志级别,直接设置根目录级别;
logging.level.com.wsm=trace
# 指定控制台输出消息格式
logging.pattern.console=[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c [%thread]===== %msg %n
# 指定存放日志文件的具体路径
# logging.file 已经过期的,因为这样设置不方便管理..
# logging.file=/logs/springboot.log
# logging.file.path 可以指定日志文件存放的目录,默认的文件名 spring.log  具有更好的辨识度了;
logging.file.path=/logs/springboot/
# 指定日志文件消息格式
logging.pattern.file=[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c [%thread]===== %msg %n
# Springboot 提供的配置文件不够...只能表示基本的日志配置!

image-20211223152950422

SpringBoot 解析配置文件:

Springboot 默认的配置文件,仅支持基本的日志配置

给类路径下放上每个日志框架自己的配置文件;SpringBoot就不使用默认配置的了

日志框架配置文件
Logbacklogback-spring.xml 或 logback.xml
Log4j2log4j2-spring.xml 或 log4j2.xml
JULlogging.properties

logback-spring.xml 举例子:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] -------- %m %n"></property>

    <!--控制台日志输出的 appender-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--控制输出流对象 默认 System.out 改为 System.err-->
        <target>System.err</target>
        <!--日志消息格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- <springProfile>: SpringBoot 支持使用这样的对日志进行设置不同的场景~ -->
            <!-- 开发环境 -->
            <springProfile name="dev">
                <pattern>${pattern}</pattern>
            </springProfile>
            <!-- 生产环境 -->
            <springProfile name="pro">
                <pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] xxxxxxxx %m %n</pattern>
            </springProfile>
        </encoder>
    </appender>

    <!--自定义 looger 对象  additivity="false" 自定义 logger 对象是否继承 rootLogger -->
    <logger name="com.wsm" level="info" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>

application.properties 可以动态调节,生产环境pro 和 开发环境dev

# 指定项目使用的具体环境
spring.profiles.active=pro

来回切换环境, 查看运行效果😀

dev
[ERROR] 2021-12-23 15:51:26.148 com.wsm.demo.DemoApplicationTests contextLoads 15 [main] -------- error 
[WARN ] 2021-12-23 15:51:26.149 com.wsm.demo.DemoApplicationTests contextLoads 16 [main] -------- warn 
[INFO ] 2021-12-23 15:51:26.149 com.wsm.demo.DemoApplicationTests contextLoads 17 [main] -------- info 
[DEBUG] 2021-12-23 15:51:26.149 com.wsm.demo.DemoApplicationTests contextLoads 18 [main] -------- debug 
[TRACE] 2021-12-23 15:51:26.149 com.wsm.demo.DemoApplicationTests contextLoads 19 [main] -------- trace 

pro
[ERROR] 2021-12-23 15:52:03.623 com.wsm.demo.DemoApplicationTests contextLoads 15 [main] xxxxxxxx error 
[WARN ] 2021-12-23 15:52:03.623 com.wsm.demo.DemoApplicationTests contextLoads 16 [main] xxxxxxxx warn 
[INFO ] 2021-12-23 15:52:03.623 com.wsm.demo.DemoApplicationTests contextLoads 17 [main] xxxxxxxx info 
[DEBUG] 2021-12-23 15:52:03.623 com.wsm.demo.DemoApplicationTests contextLoads 18 [main] xxxxxxxx debug 
[TRACE] 2021-12-23 15:52:03.623 com.wsm.demo.DemoApplicationTests contextLoads 19 [main] xxxxxxxx trace

切换日志 为 log4j2

首先,移除logback 实现依赖,添加log4j2 的依赖~

image-20211223155840286

添加 log4j2的依赖配置:pom.xml

<dependencies>
    <!-- SpringBoot web starter 的集中依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!--idea 执行后,排除 logback 日志实现-->
        <exclusions>
            <exclusion>
                <artifactId>spring-boot-starter-logging</artifactId>
                <groupId>org.springframework.boot</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- SpringBoot test starter 的集中依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!--使用 log4j2 的日志启动器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
</dependencies>

输出日志信息就是 log4j2 的信息了

[ERROR] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== error 
[WARN ] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== warn 
[INFO ] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== info 
[DEBUG] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== debug 
[TRACE] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== trace 
[INFO ] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== log4j2 info 

添加对应的配置文件: log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
    status="warn" 日志框架本身的输出日志级别
    monitorInterval="5" 自动加载配置文件的间隔时间,不低于 5 秒
-->
<Configuration status="trace" monitorInterval="5">
    <!--日志处理-->
    <Appenders>
        <!--控制台输出 appender-->
        <Console name="Console" target="SYSTEM_ERR">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L -------------- %m%n" />
        </Console>
    </Appenders>

    <!--logger 定义-->
    <Loggers>
        <!--使用 rootLogger 配置 日志级别 level="trace"-->
        <Root level="trace">
            <!--指定日志使用的处理器-->
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

重新执行输出的信息就是, 配置文件中的配置 红色字体, ------------- 间隔;

ok ,到这里日志介绍就完毕了, 感谢观看! 😀📺

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java.慈祥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值