笔记学习原视频(B
站):全面深入学习多种java日志框架
目前常见日志框架有:
JUL
Logback
log4j
log4j2
目前常见的日志门面(统一的日志API
):
JCL
Slf4j
一、 老技术(基本都弃用了)
之所以要了解这种废弃的技术一是方便了解整体的发展历史,清楚技术替换的缘由,二是方便了解日志系统的基本原理。
日志门面技术:JCL
日志框架:
JDK
原生的JUL
Apache
的Log4J
1 JUL
日志框架
JUL
,全称Java Util Logging
,是Java
平台自带的原生日志框架。它不需要额外引入第三方库,因此在小型应用或快速开发项目中非常受欢迎。
JUL
日志组件
Loggers
:为日志记录器,日志对象,通常为应用程序访问日志框架的入口。Appenders
:也叫Handlers
为处理器,用于处理输出,比如输出到控制台还是文件等。Layouts
:也叫Formatters
,负责将日志中的数据进行转换和格式化。决定了数据在一条日志记录中的最终形式。Level
:日志级别,Trace < Debug < Info < Warn < Error < Fatal < Off
。Filters
:过滤器,根据需要定制哪些记录会被过滤,哪些会被记录。
1.1 快速入门
JUL(Java Util Logger)
这是Java
自带的日志框架,不需要引入任何依赖。
public class JulDemo { // 1. 获取日志记录器 public static final Logger logger = Logger.getLogger("com.clcao.log.JulDemo"); public static void main(String[] args) { // 2. 日志记录输出 logger.info("info信息"); // 3. 通用方法进行日志记录 logger.log(Level.INFO,"Level Info msg"); // 4. 通过占位符,输出变量信息 String name = "张三"; int age = 18; logger.log(Level.INFO,"用户信息,name = {0},age = {1}",new Object[]{name,age}); }
}
注意这里的包为java.util.logging
下的包。
输出大概长这样👇
1.2 日志级别
在java.util.logging.Level
有定义:
OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL
默认级别INFO
@Test
public void testLevel(){ Logger logger = Logger.getLogger("com.clcao.log.JulTest"); // 日志级别 logger.severe("SEVERE"); logger.warning("WARNING"); logger.info("INFO"); // JUL 默认级别 logger.config("CONFIG"); logger.fine("FINE"); logger.finer("FINER"); logger.finest("FINEST");
}
1.2.1 自定义日志级别
@Test
public void testSetLevel(){ Logger logger = Logger.getLogger("com.clcao.log.JulTest"); // 关闭原有的日志级别 logger.setUseParentHandlers(false); // 创建处理器对象,在 Logback 里边对应 appender ConsoleHandler consoleHandler = new ConsoleHandler(); // 创建格式化对象 Formatter SimpleFormatter formatter = new SimpleFormatter(); // 关联 consoleHandler.setFormatter(formatter); logger.addHandler(consoleHandler); // 设置日志级别 consoleHandler.setLevel(Level.ALL); logger.setLevel(Level.ALL); // 日志级别 logger.severe("SEVERE"); logger.warning("WARNING"); logger.info("INFO"); // JUL 默认级别 logger.config("CONFIG"); logger.fine("FINE"); logger.finer("FINER"); logger.finest("FINEST");
}
1.2.2 文件日志输出
在上面的基础上追加处理器FileHandler
// 文件日志输出
String path = "logs/jul.log";
File file = new File(path);
System.out.println(file.getParentFile());
if (!file.getParentFile().exists()) { System.out.println(file.getParentFile().mkdirs());
} FileHandler fileHandler = new FileHandler(path); fileHandler.setFormatter(formatter);
logger.addHandler(fileHandler);
1.3 父子容器
日志打印默认继承父容器日志级别。
@Test
public void testParent(){ Logger logger1 = Logger.getLogger("com"); // 父为:java.util.logging.LogManager$RootLoggerLogger logger2 = Logger.getLogger("com.clcao"); // 父为 logger1Logger logger3 = Logger.getLogger("com.clcao.log"); // 父为 logger2System.out.println(logger3.getParent() == logger2); System.out.println(logger2.getParent() == logger1); System.out.println(logger1.getParent()); // 设置日志级别 ConsoleHandler consoleHandler = new ConsoleHandler(); SimpleFormatter simpleFormatter = new SimpleFormatter(); consoleHandler.setFormatter(simpleFormatter); logger2.setLevel(Level.ALL); consoleHandler.setLevel(Level.ALL); logger2.addHandler(consoleHandler); // 关闭父容器 logger2.setUseParentHandlers(false); // 如果不设置false会继承父级别 logger1==>默认日志级别为 INFOprintLog(logger2);
}
1.4 配置文件
默认由日志管理器LogManager
读取配置文件,位置:System.getProperty("java.home")
下的/conf/logging.properties
。
当前为:/Library/Java/JavaVirtualMachines/jdk-17.0.2.jdk/Contents/Home/conf/logging.properties
即JDK
安装目录下的conf
目录,自带的配置如下:
logging.properties
(去掉了相关注释)
# 默认处理器对象,向控制台输出
handlers= java.util.logging.ConsoleHandler # 顶级 LoggerManager 没有名称,所以为 .level 默认级别为 INFO
.level= INFO # 日志文件输出路径设置
# %h当前用户名 %u数字取值
java.util.logging.FileHandler.pattern = %h/java%u.log
# 最多 50000 条记录
java.util.logging.FileHandler.limit = 50000
# 日志文件数量最大为 1
java.util.logging.FileHandler.count = 1 java.util.logging.FileHandler.maxLocks = 100
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter # Limit the messages that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
读取配置文件
@Test
public void testConfig() throws IOException { Logger logger = Logger.getLogger("com.clcao"); logger.addHandler(new FileHandler()); // 读取配置文件 InputStream is = JulTest.class.getClassLoader().getResourceAsStream("logging.properties"); // 创建 LogManager 对象 读取配置文件 LogManager logManager = LogManager.getLogManager(); logManager.readConfiguration(is); printLog(logger);
}
如果创建了FileHandler
文件日志处理器,则会根据配置文件的设置,生成文件日志,默认配置为java.util.logging.FileHandler.pattern = %h/java%u.log
。因此在当前用户目录下存在文件:java0.log
配置日志文件输出
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
日志文件
XML
格式
配置文件详解
# RootLogger 顶级父元素的默认日志处理器,如果要日志文件输出则:java.util.logging.FileHandler
handlers= java.util.logging.ConsoleHandler# RootLogger 顶级父元素日志级别
.level= ALL # 自定义 Logger 使用
com.clcao.handlers = java.util.logging.ConsoleHandler
com.clcao.level = CONFIG# 关闭默认设置
com.clcao.useParentHandlers = false# 日志文件输出路径设置
# %h当前用户名 %u数字取值
java.util.logging.FileHandler.pattern = %h/java%u.log
# 最多 50000 条记录
java.util.logging.FileHandler.limit = 50000
# 日志文件数量最大为 1
java.util.logging.FileHandler.count = 1 java.util.logging.FileHandler.maxLocks = 100
# 指定文件日志输出的格式对象,可以替换为 SimpleFormatter
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# 指定日志文件追加
java.util.logging.FileHandler.append = true# 向控制台输出的日志对象 ConsoleHandler
# 控制台输出对象日志级别
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
1.5 执行原理和流程
2 Log4J
日志框架
Log4J
是Apache
软件基金会下的一个开源项目,主要用于Java应用程序中的日志记录。
Log4J
最早在1996年被创建,随后经过多次的改进和增强,逐渐成为了Java
日志领域的重要框架之一。需要注意的是,随着技术的发展,Log4J
已经发展到了Log4J2
,但这里主要介绍的是Log4J
(通常指的是Log4J 1.x
版本)。
官网:Apache Log4J
Log4J
技术特点
-
灵活的配置:
Log4J
允许通过配置文件(如log4j.properties
或log4j.xml
)来灵活地控制日志信息的输出目的地(如控制台、文件、GUI
组件等)、输出格式以及日志级别等,而无需修改应用程序的代码。 -
多输出源支持:
Log4J
支持将日志信息输出到多个目的地,包括但不限于控制台、文件、套接口服务器、NT的事件记录器、UNIX Syslog守护进程等。 -
日志级别控制:
Log4J
定义了多种日志级别(如OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
等),允许开发者根据日志信息的重要性来选择性地输出日志,从而有效地控制日志信息的生成和输出。 -
线程安全:
Log4J
是线程安全的,这意味着它可以在多线程环境中安全地使用,而不会出现数据竞争或不一致的问题。 -
性能优异:
Log4J
在日志记录方面表现出色,具有较高的性能和可靠性,能够满足大规模应用程序的日志记录需求。 -
扩展性强:
Log4J
提供了丰富的API和扩展点,允许开发者根据自己的需求来定制和扩展日志记录功能。 -
支持多种语言:虽然
Log4J
最初是为Java设计的,但随着时间的推移,它已经被孵化出了支持C、C++、C#、Perl、Python、Ruby
等多种语言的子框架或接口,使得多语言分布式系统能够使用统一的日志组件模块。 -
易于集成:
Log4J
易于与Java应用程序集成,并且提供了丰富的第三方扩展,可以方便地将其集成到J2EE、JINI
甚至SNMP
等应用中。
2.1 快速入门
导入依赖
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version>
</dependency>
log4j
:整个日志框架的总称,包含核心组件、API
和可能的集成库。log4j-core
:Log4j
框架的核心组件,提供日志记录和输出的具体实现。log4j-api
:Log4j
的API
接口部分,定义了一系列用于日志记录的接口。log4j-jul
(或类似功能的库):与JUL
集成的库,使得Log4j
能够兼容和集成JDK自带的日志框架。
快速入门
@Test
public void testQuick(){ // 初始化配置信息,否则会报错 BasicConfigurator.configure(); // 1. 获取日志记录器 Logger logger = Logger.getLogger(Log4JTest.class); Logger rootLogger = Logger.getRootLogger(); System.out.println(rootLogger.getLevel()); // 2. 日志记录输出 logger.info("INFO"); // 3. 日志级别 logger.log(Level.OFF,"OFF"); logger.log(Level.FATAL,"FATAL"); logger.log(Level.ERROR,"ERROR"); logger.log(Level.WARN,"WARN"); logger.log(Level.INFO,"INFO"); logger.log(Level.DEBUG,"DEBUG"); logger.log(Level.TRACE,"TRACE");
}
输出长这样👇
2.1.1 Log4J
日志组件
Loggers
:日志记录器,控制日志的输出级别和是否输出Appenders
:输出端,跟JUL
中的handlers
处理器一个意思,指定日志的输出方式(控制台、文件)Layout
:日志格式化器,等价于JUL
中的Formatter
,控制日志信息输出的格式。
Log4J
有一个特殊的logger
叫root
,他是所有logger
的根,意味着所有的logger
直接或者间接继承自root
,可以通过Logger.getRootLogger();
方法获取,默认级别为DEBUG
。
Appenders
输出端类型 | 作用 |
---|---|
ConsoleAppender | 将日志输出到控制台 |
FileAppender | 将日志输出到文件 |
DailyRollingFileAppender | 将日志输出到文件,并且每天输出一个新文件 |
RollingFileAppender | 将日志输出到文件,并且指定文件的大小,当文件达到指定大小时候改名生成新的文件 |
JDBCAppender | 将日志信息保存到数据库中 |
Layouts
格式化器类型 | 作用 |
---|---|
HTMLLayout | 格式化日志输出为HTML 格式 |
SimpleLayout | 简单的格式化输出,打印格式为(info-message) |
PatternLayout | 最强大的格式化器,自定义格式化输出,如果没有则使用默认格式 |
简单配置文件
必须在类路径下,文件名为log4j.properties
。
log4j.rootLogger = trace,console
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.SimpleLayout
使用
@Test public void testQuick(){ // 初始化配置信息,否则会报错
// BasicConfigurator.configure(); // 注释掉初始化信息,改用读取配置文件 ==> log4j.properties // 1. 获取日志记录器 Logger logger = Logger.getLogger(Log4JTest.class); Logger rootLogger = Logger.getRootLogger(); System.out.println(rootLogger.getLevel()); // 2. 日志记录输出 logger.info("INFO"); // 3. 日志级别 logger.log(Level.OFF,"OFF"); logger.log(Level.FATAL,"FATAL"); logger.log(Level.ERROR,"ERROR"); logger.log(Level.WARN,"WARN"); logger.log(Level.INFO,"INFO"); logger.log(Level.DEBUG,"DEBUG"); logger.log(Level.TRACE,"TRACE"); }
输出
2.1.2 内置日志
开启
// 开启 log4j 内置日志
LogLog.setInternalDebugging(true);
2.2 配置文件详解
log4j.properties
# 指定 RootLogger 顶级父元素的日志设置
# 指定顶级父元素的日志级别 trace, 输出位置为 console 控制台,需要指定下面的appender,比如下面的 file | rollingFile | dailyRollingFilelog4j.rootLogger = trace,logDB
# 指定日志输出的 Appender对象
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定日志输出的格式化器 HTMLLayout | PatternLayout | xm.XMLLayoutlog4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.console.layout.conversionPattern = [%p]%r %c %t %F %l %d{yyyy-MM-dd HH:mm:ss.SSS} - %m%n # %m 输出代码中指定的信息
# %p 输出优先级, DEBUG、INFO 等
# %n 换行符(Windows 换行符为`\r\n`,Unix 换行符为`\n`)
# %r 输出自应用启动到输出该条日志所消耗的时间毫秒数
# %c 输出打印该日志的类全限定名
# %t 输出该日志的线程名
# %d 输出服务器的当前时间,默认为ISO8601,也可以指定格式,比如:%d{yyyy-MM-dd HH:mm:ss}
# %l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。比如:Test.main(Test.java:10)
# %F 输出日志信息产生所在的文件名称
# %L 输出代码中的行号
# %% 输出一个 `%` 符号 ################################## 普通 FileAppender ################################ 指定日志文件输出的 Appender 对象
log4j.appender.file = org.apache.log4j.FileAppender
# 指定消息格式器对象 Layoutlog4j.appender.file.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.file.layout.conversionPattern = [%p]%r %c %t %F %l %d{yyyy-MM-dd HH:mm:ss.SSS} - %m%n
# 指定日志文件的保存路径
log4j.appender.file.file = logs/log4j.log
# 指定日志的字符集
log4j.appender.file.encoding = UTF-8 ################################## 日志回滚 RollingFileAppender ####################### 指定日志文件输出的 Appender 对象
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
# 指定消息格式器对象 Layoutlog4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.rollingFile.layout.conversionPattern = [%p]%r %c %t %F %l %d{yyyy-MM-dd HH:mm:ss.SSS} - %m%n
# 指定日志文件的保存路径
log4j.appender.rollingFile.file = logs/log4j.log
# 指定日志的字符集
log4j.appender.rollingFile.encoding = UTF-8
# 指定日志文件的内容大小
log4j.appender.rollingFile.maxFileSize = 1MB
# 指定日志文件的数量
log4j.appender.rollingFile.maxBackupIndex = 10 ################################## 时间规则 DailyRollingFileAppender ####################### 指定日志文件输出的 Appender 对象
log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
# 指定消息格式器对象 Layoutlog4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.dailyRollingFile.layout.conversionPattern = [%p]%r %c %t %F %l %d{yyyy-MM-dd HH:mm:ss.SSS} - %m%n
# 指定日志文件的保存路径
log4j.appender.dailyRollingFile.file = logs/log4j.log
# 指定日志的字符集
log4j.appender.dailyRollingFile.encoding = UTF-8
# 指定拆分规则
log4j.appender.dailyRollingFile.datePattern = yyyy-MM-dd-HH-mm-ss ################################## 数据库 JDBCAppender ######################log4j.appender.logDB = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout = org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver = com.mysql.jdbc.Driver
log4j.appender.logDB.URL = jdbc:mysql://localhost:3306/clcao
log4j.appender.logDB.User = root
log4j.appender.logDB.Password = ccl@2023
log4j.appender.logDB.Sql = INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message)\ values('clcao','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
JDBCAppender
建表语句
CREATE TABLE IF NOT EXISTS `log`(log_id int(11) NOT NULL AUTO_INCREMENT,`project_name` varchar(255) DEFAULT NULL COMMENT '项目名',`create_date` varchar(255) DEFAULT NULL COMMENT '创建时间',`levle` 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`)
);
对应的设置👇
################################## 数据库 JDBCAppender ######################log4j.appender.logDB = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout = org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver = com.mysql.jdbc.Driver
log4j.appender.logDB.URL = jdbc:mysql://localhost:3306/clcao
log4j.appender.logDB.User = root
log4j.appender.logDB.Password = ccl@2023
log4j.appender.logDB.Sql = INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message)\ values('clcao','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
自定义logger
日志
配置文件添加
# 指定 RootLogger 顶级父元素的日志设置
# 指定顶级父元素的日志级别 trace, 输出位置为 console 控制台,需要指定下面的appender,比如下面的 file | rollingFile | dailyRollingFile
log4j.rootLogger = trace,rollingFile # 自定义 logger 日志
log4j.logger.com.clcao = info,file
对比顶级父元素的日志设置。自定义 logger
意味着指定某个包下产生的日志记录方式,而顶级父元素root
记录的是整个项目。
顶级父元素 Root Logger
- 定义:
root
logger 是 Log4j 层次结构中的顶级节点。它不对应于应用程序中的任何特定类,而是作为所有其他 logger 的默认父节点。 - 配置:在 Log4j 的配置中,
root
logger 的配置通常指定了全局的日志级别和输出目的地(如控制台、文件等)。如果没有为特定的 logger 指定配置,它将继承root
logger 的配置。 - 作用:
root
logger 的设置可以作为应用程序日志记录行为的“兜底”配置。它确保了即使应用程序中没有显式配置任何 logger,日志记录仍然可以按照某种预定的方式工作。
3 JCL
日志门面
JCL(Jakarta Commons Logging)
是Apache提供的一个通用日志API,也称为Commons Logging。它主要作为一个日志门面(Facade)存在,允许开发者在编写代码时使用统一的日志接口,而在实际运行时,则可以根据项目依赖的日志框架来动态选择具体的日志实现。这样做的好处是提高了代码的灵活性和可维护性,使得开发者可以在不修改代码的情况下,通过简单的配置来切换不同的日志实现框架
3.1 快速入门
@Test
public void testQuick(){ // 获取日志记录器 Log log = LogFactory.getLog(JCLTest.class); // 日志记录 log.info("Hello JCL"); // 日志级别 log.fatal("fatal"); log.error("error"); log.warn("warn"); log.info("info"); log.debug("debug"); log.trace("trace");
}
如果没有导入Log4J
的依赖,则使用JDK
默认实现,即JUL
框架打印日志,如果导入了Log4J
依赖则使用Log4J
的实现。
这就是JCL
作为日志门面技术的好处,做到了统一API
的目的。
3.2 JCL
原理
支持的日志实现
LogFactoryImpl.java
private static final String[] classesToDiscover = { LOGGING_IMPL_LOG4J_LOGGER, "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog"
};
这里定义了支持的几种实现;其中Log4J
的常量为:org.apache.commons.logging.impl.Log4JLogger
。
通过预定义实现类,进行查找,如果找到类存在则创建实例对象,从而做到根据依赖得到不同是实现的目的,如果存在多个实现技术,则按照数组顺序执行。
但是JCL
只支持一些简单的如上面定义的一些技术,像现在主流的Logback
、Log4J2
等都不支持,并且无法扩展,因此=======》@Deprecated!
算是弃用的技术了。取而代之的门面技术是slf4j
。
二、 主流新技术
再谈日志门面和日志框架技术
日志框架出现的历史顺序:
Log4J -> JUL -> JCL -> Slf4j -> Logback -> Log4J2
1 SLF4J
日志门面
SLF4J(Simple Logging Facade for Java)
是一个为各种日志框架提供简单抽象(或称为门面)的Java库。它允许你在后端使用任何日志框架(如Logback、log4j
等),而你的应用程序代码则无需修改即可在这些日志框架之间切换。SLF4J
本身不提供日志实现,它只是一个抽象层,用于绑定到具体的日志框架上。
1.1 快速入门
添加依赖
<!--slf4j日志门面-->
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.13</version>
</dependency><!--内置的简单实现-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>2.0.13</version><scope>test</scope>
</dependency>
使用
public class Slf4jTest { private static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class); @Test public void testQuick(){ // 日志记录 LOGGER.info("Hello SLF4J!!!"); // 日志级别 LOGGER.error("error"); LOGGER.warn("warn"); LOGGER.info("info"); LOGGER.debug("debug"); LOGGER.trace("trace"); // 使用占位符输出日志信息 String name = "Tom"; int age = 19; LOGGER.info("用户信息:name={},age={}",name,age); // 将系统的异常信息输出 try { int a = 1/0; } catch (Exception e) { LOGGER.error("出现异常:", e); } }
}
1.2 绑定日志实现
slf4j
门面与日志实现框架技术官网图
场景1: 只引入slf4j-api.jar
依赖,没有引入任何其他实现,默认输出 /dev/null
也就是不会进行日志记录。
场景2: 引入遵循了SLF4J
规范的实现依赖,比如logback(logback-classic.jar、logback-core.jar)
、slf4j-simple.jar
,则可以直接使用。
场景3: 对于较早的JUL
和log4j
没有遵循规范,需要引入一个适配包,比如:slf4j-reload4j.jar
适配LOG4J
框架和slf4j-jdk14.jar
适配JUL
日志框架。
场景4: 引入slf4j-nop.jar
日志开关,将日志功能关闭(不能引入其他的实现)。
存在多个实现
<!--内置的简单实现-->
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.13</version> <scope>test</scope>
</dependency> <!--logback日志框架-->
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.11</version>
</dependency>
存在多个实现,会优先使用第一个!
绑定需要适配包的日志框架(log4j
)
引入依赖
<!--适配log4j-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-reload4j</artifactId><version>2.0.13</version><scope>test</scope>
</dependency>
<!--LOG4J日志框架-->
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version>
</dependency>
绑定日志实现原理
核心在于使用JDK
自带的ServiceLoader
类动态加载具体的实现类。
日志桥接器
场景1: 老项目使用Jcl
作为日志门面,使用LOG4J
日志框架,现在需要升级Logback
日志框架(不需要适配包)。则可以添加桥接器jcl-over-slf4j.jar
依赖。同理如果直接使用LOG4J
日志框架或者JUL
日志框架,可以使用log4j-over-slf4j.jar
桥接器。
jcl-over-slf4j.jar
替换commons-logging.jar
依赖log4j-over-slf4j.jar
替换log4j.jar
jul-to-slf4j.jar
桥接器取代直接使用JDK
的日志框架
场景2: 老项目直接使用LOG4J
日志框架(需要适配包),现在需要升级slf4j
作为门面技术,则可以添加jcl-over-slf4j.jar
替换commons-logging.jar
。
假如我现在正在使用JCL
作为门面技术使用LOG4J
日志框架,现在我需要升级为Logback
日志框架
步骤1:添加jcl-over-slf4j.jar
<!--slf4j门面-->
<dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>2.0.13</version>
</dependency><!--桥接器替换JCL门面-->
<dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>2.0.13</version>
</dependency>
步骤2:去掉commons-logging.jar
依赖、log4j.jar
依赖
步骤3:添加logback-classic.jar
依赖
<!--logback日志框架-->
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.11</version>
</dependency>
不需要任何代码改动,直接运行即可!
日志桥接器原理:
log4j-over-slf4j.jar
桥接,要保证log4j
源代码不动,以为着入口方法需要保证具有相同的包名。
import org.apache.log4j.Logger;
Logger logger = Logger.getLogger(Logger.class);
实际上,桥接包,使用的正是相同的包名,从而实现从入口调用到log4j-over-slf4j.jar
下的内容,最后根据调用链,关键步骤为:this.slf4jLogger = LoggerFactory.getLogger(name);
最终达到使用slf4j
门面的相同目的。
2 Logback
日志框架
Logback
是一个用于Java应用程序的日志框架,由log4j
框架的创始人Ceki Gülcü
开发。Logback
旨在成为log4j
的替代品,并提供了更好的性能、可扩展性和灵活性。
官网:Logback
Logback
的组成
Logback
分为三个模块:
logback-core
:提供了通用的日志记录功能,是Logback
的基础模块,也是其他两个模块的基础。logback-classic
:提供了与SLF4J API
和log4j API
兼容的日志记录功能,是log4j 1.x
的显著改进版本。logback-access
:与Tomcat
和Jetty
等Servlet
容器集成,以提供HTTP
访问日志功能。
2.1 快速入门
添加依赖
<!--logback日志框架-->
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.11</version>
</dependency>
logback-classic
间接依赖了slf4j-api.jar
因此这里没有单独引入此jar
包了。
使用
public class LogbackTest { private static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class); @Test public void testQuick(){ // 日志级别 LOGGER.error("ERROR"); LOGGER.warn("WARN"); LOGGER.info("INFO"); LOGGER.debug("DEBUG"); // 默认级别 LOGGER.trace("TRACE"); // 记录日志信息 String name = "Tom"; int age = 19; LOGGER.info("用户信息: name={},age={}",name,age); }
}
2.2 Logback
日志配置
Logback
会依次读取以下类型配置文件:
logback.groovy
logback-test.xml
logback.xml
如果均不存在,则采用默认配置。
2.2.1 Logback
组件
Logger
:日志记录器,关联到对应的context
容器,主要用于存放日志对象,也可以定义日志类型和级别。Appender
:用于指定日志的输出目的,控制台或者文件等。Layout
:用于格式化日志输出,在Logback
中被封装在encoder
中
2.2.2 配置文件详解
logback.xml
配置文件
<?xml version="1.0" encoding="UTF-8"?> <!-- 配置文件修改时重新加载,默认true -->
<configuration scan="true"> <!--配置集中管理属性--> <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5level] %c %M %L - %m%n"/> <property name="html_pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS}%thread%-5level%c%M%L%m%n"/> <property name="log_dir" value="logs"/> <!--关闭logback框架自身日志-->
<statusListener class="ch.qos.logback.core.status.NopStatusListener"/> <!-- 日志输出格式 %-5level 日志级别 %d{yyyy-MM-dd HH:mm:ss} 日期时间 %c 类的全限定名 %M method方法名 %L 行号 %thread 线程名称 %m 或者 %msg 信息 %n 换行 --> <!--控制台日志输出的 Appender --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!--控制输出流对象,默认为 System.out--> <target>System.err</target> <!--日志消息格式设置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!--日志文件输出的 Appender --> <appender name="file" class="ch.qos.logback.core.FileAppender"> <!--日志文件保存路径--> <file>${log_dir}/logback.log</file> <!--日志消息格式设置--> <encoder charset="UTF-8" class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!--HTML 格式的日志文件输出 Appender --> <appender name="htmlFile" class="ch.qos.logback.core.FileAppender"> <!--日志文件保存路径--> <file>${log_dir}/logback.html</file> <!-- html 日志消息格式设置--> <encoder charset="UTF-8" class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="ch.qos.logback.classic.html.HTMLLayout"> <pattern>${html_pattern}</pattern> </layout> </encoder> </appender> <!--日志拆分与归档压缩的 Appender --> <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--日志文件保存路径--> <file>${log_dir}/roll_logback.log</file> <!--日志消息格式设置--> <encoder charset="UTF-8" class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> <!--指定拆分的规则--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--按照时间和压缩格式声明拆分的文件名,即归档后的文件应该叫啥,比如 20240727121212.gz--> <fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern> <!--按照文件大小拆分--> <maxFileSize>1MB</maxFileSize> </rollingPolicy> <!--日志级别过滤器--> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <!--日志过滤规则--> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!--异步日志--> <appender name="async" class="ch.qos.logback.classic.AsyncAppender"> <!--指定某个具体的 Appender--> <appender-ref ref="rollingFile"/> </appender> <!--RootLogger 顶级父元素配置--> <root level="DEBUG"> <appender-ref ref="console"/> </root> <!--自定义 logger 对象 additivity=false 不继承 RootLogger --> <logger name="com.example" level="info" additivity="false"> <appender-ref ref="console"/> </logger>
</configuration>
2.3 Logback-access
模块
Logback-access 是 Logback 日志系统的一个模块,专为记录 Web 应用程序的访问日志而设计。
2.3.1 快速入门
基于Tomcat 10.x 版本
1)添加依赖
在Tomcat
目录下找到lib
目录,将common-2.0.2.jar
和logback-core-1.5.6.jar
和tomcat-2.0.2.jar
添加到里边。
2)替换tomcat
配置/conf/server.xml
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" />
将上面内容替换为:
<Valve className="ch.qos.logback.access.tomcat.LogbackValve"/>
3)在/conf
下添加logback-access.xml
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration> <!-- always a good activate OnConsoleStatusListener --> <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" /> <property name="LOG_DIR" value="${catalina.base}/logs"/> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_DIR}/access.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern> </rollingPolicy> <encoder> <pattern>combined</pattern> </encoder> </appender> <appender-ref ref="FILE" />
</configuration>
这里的
pattern
直接使用combined
是因为logback
对一些常用的模式做了统一,因此可以这样,其等价于%h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"
,具体参考下面的官网配置。
启动Tomcat
即可,可以看到Tomcat
目录下的/logs
下存在文件access.log
官方配置:access configuration
3 Log4j2
日志框架
Log4j2是Apache实现的一个开源日志框架,作为Log4j 1.x和Logback的改进版,它采用了新技术,如无锁异步等,使得日志的吞吐量、性能比Log4j 1.x提高了10倍,并解决了一些死锁的bug,同时配置也更加简单灵活。
特点:
- 异常处理,在
logback
中Appender
中的异常不会被感知,log4j2
提供了一些异常处理 - 性能提升
- 自动重载配置,修改日志级别而不需要重新启动应用
- 无垃圾机制
算是目前Java
日志框架中最优秀的了。
官网:Apache Log4j2
3.1 快速入门
Log4j2
即是门面也是日志框架,所以可以单独使用,而不依赖slf4j
,不过主流的应用还是slf4j + Log4j2
。
单独使用Log4j2
添加依赖
<!--Log4j2日志门面-->
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.23.1</version>
</dependency>
<!--Log4j2日志实现-->
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.23.1</version>
</dependency>
添加配置文件log4j2.xml
^a0a627
<?xml version="1.0" encoding="UTF-8"?> <Configuration name="ConfigTest" status="ERROR" monitorInterval="5"> <!--集中属性配置管理--> <properties> <property name="LOG_DIR" value="logs"/> <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L - %m%n"/> </properties> <Appenders> <!--控制台输出--> <Console name="console" target="SYSTEM_OUT"> <PatternLayout pattern="${pattern}"/> </Console> <!--日志文件输出--> <File name="file" fileName="${LOG_DIR}/log4j2.log"> <PatternLayout pattern="${pattern}"/> </File> </Appenders> <Loggers> <Root level="all"> <AppenderRef ref="console"/> </Root> </Loggers>
</Configuration>
使用
public class Log4j2Test { private static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class); @Test public void testQuick(){ // 日志级别 LOGGER.fatal("FATAL"); LOGGER.error("ERROR"); // 默认级别 LOGGER.warn("WARN"); LOGGER.info("INFO"); LOGGER.debug("DEBUG"); LOGGER.trace("TRACE"); // 记录日志信息 String name = "Tom"; int age = 18; LOGGER.info("用户信息:name={}, age={}",name,age); }
}
输出结果
slf4j+Log4j2
(主流推荐)
添加依赖
<!--slf4j日志门面-->
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.13</version>
</dependency>
<!--log4j2的适配器 适配slf4j门面-->
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j2-impl</artifactId> <version>2.23.1</version> <scope>test</scope>
</dependency>
<!--Log4j2日志门面-->
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.23.1</version>
</dependency>
<!--Log4j2日志实现-->
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.23.1</version>
</dependency>
基本关系为:
调用slf4j-api
-> 调用适配器log4j-slf4j2-impl
-> 调用Log4j2
门面log4j-api
->调用Log4j2
的日志实现log4j-core
。
添加配置文件(同上)log4j2.xml
使用
public class Log4j2Test { private static final Logger LOGGER = LoggerFactory.getLogger(Log4j2Test.class); @Test public void testQuick2(){ // 日志记录 LOGGER.info("Hello SLF4J!!!"); // 日志级别 LOGGER.error("error"); LOGGER.warn("warn"); LOGGER.info("info"); LOGGER.debug("debug"); LOGGER.trace("trace"); // 使用占位符输出日志信息 String name = "Tom"; int age = 19; LOGGER.info("用户信息:name={},age={}",name,age); }
}
3.2 配置文件详解
<?xml version="1.0" encoding="UTF-8"?>
<!-- status: 日志框架本身的日志级别 monitorInterval: 自动加载配置文件的间隔时间,不低于5秒
-->
<Configuration name="ConfigTest" status="DEBUG" monitorInterval="5"> <!--集中属性配置管理--> <properties> <property name="LOG_DIR" value="logs"/> <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L - %m%n"/> </properties> <!-- Appender 日志处理器--> <Appenders> <!--控制台输出--> <Console name="console" target="SYSTEM_OUT"> <PatternLayout pattern="${pattern}"/> </Console> <!--日志文件输出--> <File name="file" fileName="${LOG_DIR}/log4j2.log"> <PatternLayout pattern="${pattern}"/> </File> <!--异步的 Appender--> <Async name="Async"> <Appender-Ref ref="file"/> </Async> <!--随机读写流的日志文件输出,性能高--> <RandomAccessFile name="accessFile" fileName="${LOG_DIR}/log4j2_acc.log"> <PatternLayout pattern="${pattern}"/> </RandomAccessFile> <!-- 按照规则拆分和归档的日志文件输出 filePattern: 归档日志文件名规则 --> <RollingFile name="rollingFile" fileName="${LOG_DIR}/log4j2_roll.log" filePattern="${LOG_DIR}/$${date:yyyy-MM-dd}/rolling-%d{yyyy-MM-dd-HH-mm}-%i.log"> <!--日志级别过滤器--> <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/> <!--日志消息格式--> <PatternLayout pattern="${pattern}"/> <!--拆分规则--> <Policies> <!--系统启动时候触发拆分规则,产生一个新的日志文件--> <OnstartupTriggeringPolicy/> <!--按照文件大小拆分--> <SizeBasedTriggeringPolicy size="10MB"/> <!--按照时间的节点拆分,基于上面的filePattern--> <TimeBasedTriggeringPolicy/> </Policies> <!--在同一个目录下,文件存在的最大数量--> <DefaultRolloverStrategy max="30"/> </RollingFile> </Appenders> <!-- logger 定义--> <Loggers> <!--顶级父元素 RootLogger 定义--> <Root level="debug"> <AppenderRef ref="console"/> </Root> <!-- 混合异步需要关闭全局异步,即log4j2.component.properties设置关闭,可注释掉也可直接删除文件 自定义异步 Logger 对象 includeLocation="false" 关闭行号信息,开启可能导致性能比同步还差 additivity="false" 不再继承 RootLogger 对象 --> <AsyncLogger name="com.clcao" level="trace" includeLocation="false" additivity="false"> <AppenderRef ref=""/> </AsyncLogger> <!--自定义的logger, additivity=false 不继承父logger的日志级别 --> <Logger name="com.example" level="trace" additivity="false"> <AppenderRef ref=""/> </Logger> </Loggers>
</Configuration>
3.3 异步日志
log4j2
性能提升得益于异步日志,使用异步日志之前,先了解一下日志流程和同步与异步流程的区别。
异步日志参考手册
同步日志
同步日志流程图
异步日志
异步日志流程图
在异步日志中,Logger
对象只要产生了日志事件LogEvent
将事件添加到阻塞队列ArrayBlockingQueue
就可以继续往下执行了,可见效率会比同步高很多。
官网性能对比图
异步的实现分两种,Logger
异步和Appender
异步实现,从性能分析图👆可见,AsyncAppender
的性能提升是很低的,所以主要是使用Loggers async
。
Logger
异步实现又分两种:
- 全局异步
- 混合异步
异步日志使用
引入依赖
<!--异步日志依赖-->
<dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.4.2</version>
</dependency>
1)AsyncAppender
配置文件
<!--异步的 Appender-->
<Async name="Async"> <Appender-ref ref="file"/>
</Async>
在<Appenders>
标签内添加,引用file
这个Appender
,也就是说,对于名称为file
的Appender
将进行异步日志输出。
使用异步日志
<Root level="debug"> <AppenderRef ref="console"/> <AppenderRef ref="Async"/>
</Root>
在<Loggers>
标签内进行引用Async
的Appender
即可配置完毕!
2)AsyncLogger
全局异步
全局异步就是所有的日志信息都通过异步日志输出,不需要修改任何配置文件,之需要添加一个配置文件log4j2.component.properties
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
混合异步
混合异步就是在应用程序中即使用异步Logger
记录日志又使用同步记录日志。
关闭全局异步(这里直接注释掉了):
# Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
添加配置(在<Loggers>
标签内):
<!-- 混合异步需要关闭全局异步,即log4j2.component.properties设置关闭,可注释掉也可直接删除文件 自定义异步 Logger 对象 includeLocation="false" 关闭行号信息,开启可能导致性能比同步还差 additivity="false" 不再继承 RootLogger 对象
-->
<AsyncLogger name="com.clcao" level="trace" includeLocation="false" additivity="false"> <AppenderRef ref="rollingFile"/>
</AsyncLogger>
混合异步注意:
AsyncAppender
和全局异步和混合异步不要同时使用,会导致性能最低,也就是AsyncAppedner
所以使用某一种记得关闭其他两种的使用。- 混合异步中
includeLocation="false"
可关闭行号信息,开启可能导致性能比同步还差。
3.4 Log4j2
性能
无垃圾记录
从log4j2 2.6
版本(包括)开始通过重用对象和缓存使用,从而实现减少GC
提升性能。
Log4j2
设计了一套无垃圾机制,通过重用对象和内存缓冲来减少因频繁日志收集导致的垃圾回收(GC)调用。这意味着在日志记录过程中,尽可能重用已经存在的对象,而不是每次都创建新的对象。这可以减少堆内存的分配和释放,从而降低GC的开销。
异步日志
日志过滤
4 SpringBoot
日志设计结构
依赖关系图:
总结:
Springboot
默认使用logback
作为日志实现- 使用
slf4j
作为日志门面 - 将
jul
也转为slf4j
- 也可以使用
log4j2
作为日志门面,但最终也是调用slf4j
调用logback
实现
4.1 快速入门
直接使用
@SpringBootTest
class SpringbootLogApplicationTests { private static final Logger LOGGER = LoggerFactory.getLogger(SpringbootLogApplicationTests.class); @Test void contextLoads() { // 日志级别 LOGGER.error("ERROR"); LOGGER.warn("WARN"); LOGGER.info("INFO"); // 默认级别 LOGGER.debug("DEBUG"); LOGGER.trace("TRACE"); // 日志记录信息 String name = "Tom"; int age = 18; LOGGER.info("用户信息:name={}, age={}",name,age); // 使用 log4j2 门面 通过桥接器(log4j-to-slf4j.jar) ==> 最后使用 slf4j 门面 最终使用 logback 实现 // 所谓门面,就是对外使用的 API 是哪一套的 org.apache.logging.log4j.Logger logger = LogManager.getLogger(SpringbootLogApplicationTests.class); logger.info("log4j2 info");}
}
4.2 配置文件
# 自定义 Logger 对象
logging.level.com.clcao = trace # 指定控制台输出格式
logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5level] %c %M %L - %m%n # 指定文件日志的路径,默认在该路径下生成spring.log文件
logging.file.path= logs/
# 指定文件日志的文件名称,不能和logging.file.path同时配置,同时配置优先文件名生效
logging.file.name= logs/springLog.log
指定配置
如果需要使用自己的配置文件,而不是用springboot
的默认配置,可以在类路径下添加以下文件:
日志框架 | 配置文件 |
---|---|
Logback | logback-spring.xml 、logback.xml |
Log4j2 | log4j2-spring.xml 、log4j2.xml |
JUL | logging.properties |
logback.xml
配置参考:2.2.2 配置文件详解
log4j2.xml
配置参考:3.2 配置文件详解
logging.properties
配置参考:配置文件详解
关于logback-spring.xml
和logback.xml
的区别
logback-spring.xml
会交给spring
解析,交给spring
解析的好处是可以配置环境profile
,这样就可以配合application.properties
灵活切换环境了。
修改logback-spring.xml
<!--日志消息格式设置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <SpringProfile name="dev"> <pattern>dev-${pattern}</pattern> </SpringProfile> <SpringProfile name="pro"> <pattern>pro-${pattern}</pattern> </SpringProfile>
</encoder>
设置application.properties
spring.profiles.active=dev
这样通过切换属性就可以达到不同的日志记录效果。
4.3 切换日志实现
默认logback
日志框架,切换log4j2
日志框架。
排除logaback
依赖并且添加log4j2
依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions>
</dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
spring-boot-starter-log4j2
依赖图
基本同slf4j+Log4j2
(主流推荐)
从slf4j-api
门面入口,找到适配器log4j-slf4j-impl
然后由桥接器找到log4j2
的日志门面log4j-api
最后到具体实现log4j-core
。
这样就可以直接使用了!