20210916 Java 日志框架

思维导图

img

日志简介

日志框架的作用

  1. 控制日志输出的内容和格式
  2. 控制日志输出的位置
  3. 日志文件相关的优化,如异步操作、归档、压缩
  4. 日志系统的维护
  5. 面向接口开发 – 日志的门面

市面流行的日志框架

日志框架 描述
JUL java util logging ,Java 原生日志框架,亲儿子
Log4j Apache 的一个开源项目
Logback 由 Log4j 之父做的另一个开源项目,一个可靠、通用且灵活的 Java 日志框架
Log4j2 Log4j 官方的第二个版本,各个方面都是与 Logback 及其相似
具有插件式结构、配置文件优化等特征
Spring Boot 1.4 版本以后就不再支持 Log4j ,所以第二个版本营运而生
JCL Apache 提供的一个通用日志 API
SLF4J 简单日志门面

日志门面和日志框架的区别

日志框架技术

JUL、Logback、Log4j、Log4j2

用来方便有效地记录日志信息

日志门面技术

JCL、SLF4j

为什么要使用日志门面技术:

  • 每一种日志框架都有自己单独的API,要使用对应的框架就要使用对应的API,这就大大的增加了应用程序代码对于日志框架的耦合性
  • 我们使用了日志门面技术之后,对于应用程序来说,无论底层的日志框架如何改变,应用程序不需要修改任意一行代码,就可以直接上线了

JUL

JUL 简介

JUL 全称 Java Util Logging ,它是 Java 原生的日志框架,使用时不需要另外引用第三方的类库,相对其他的框架使用方便,学习简单,主要是使用在小型应用中。

img

  • Logger :被称为记录器,应用程序通过获取Logger对象,抵用其API来发布日志信息。Logger通常被认为是访问日志系统的入口程序
  • Handler :处理器,每个Logger都会关联一个或者是一组Handler,Logger会将日志交给关联的Handler去做处理,由Handler负责将日志做记录。Handler具体实现了日志的输出位置,比如可以输出到控制台或者是文件中等等
  • Filter :过滤器,根据需要定制哪些信息会被记录,哪些信息会被略过。
  • Formatter :格式化组件,它负责对日志中的数据和信息进行转换和格式化,所以它决定了我们输出日志最终的形式。
  • Level :日志的输出级别,每条日志消息都有一个关联的级别。我们根据输出级别的设置,用来展现最终所呈现的日志信息。根据不同的需求,去设置不同的级别。

使用说明

日志级别

JUL 日志的级别,参考 java.util.logging.Level

  • SEVERE : 错误 --- 最高级的日志级别
  • WARNING : 警告
  • INFO : (默认级别)消息
  • CONFIG : 配置
  • FINE : 详细信息(少)
  • FINER : 详细信息(中)
  • FINEST : 详细信息 (多) --- 最低级的日志级别

java.util.logging.Level 类中的属性 value 的意义在于,如果我们设置的日志的级别是 INFO(800),那么最终展现的日志信息,必须是数值大于800的所有的日志信息,以上级别,value 从高到低

两个特殊的级别:

  • OFF :可用来关闭日志记录,value 值为 Integer.MAX_VALUE
  • ALL :启用所有消息的日志记录,value 值为 Integer.MIN_VALUE

两个特殊的级别:

  • OFF :可用来关闭日志记录,value 值为 Integer.MAX_VALUE
  • ALL :启用所有消息的日志记录,value 值为 Integer.MIN_VALUE

Logger 和 Handler

用户使用Logger来进行日志的记录,Logger可以持有多个处理器Handler(日志的记录使用的是Logger,日志的输出使用的是Handler)

添加了哪些Handler对象,就相当于需要根据所添加的Handler

通过 Handler 将日志输出到指定的位置上,例如控制台、文件..

一个 Logger 可以配置多个 Handler ,Handler 可以指定日志输出的级别

Logger 之间的父子关系

JUL中Logger之间是存在"父子"关系的,值得注意的是,这种父子关系不是我们普遍认为的类之间的继承关系,父子关系是通过树状结构存储的

JUL在初始化时会创建一个顶层 RootLogger 作为所有 Logger 的父 Logger ,RootLogger 对象作为树状结构的根节点存在的

父 Logger 所做的设置,也能够同时作用于子 Logger

配置文件

默认配置文件位置:java.home\jre\lib\logging.properties

原理分析

JUL日志框架使用方式总结(原理解析)

  1. 初始化 LogManager

    1. LogManager 加载 logging.properties 配置文件
    2. 添加 LoggerLogManager
  2. 从单例的 LogManager 获取 Logger

  3. 设置日志级别 Level ,在打印的过程中使用到了日志记录的 LogRecord

  4. Filter 作为过滤器提供了日志级别之外更细粒度的控制

  5. Handler 日志处理器,决定日志的输出位置,例如控制台、文件...

  6. Formatter 是用来格式化输出的

实战案例

简单使用

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


Logger logger = Logger.getLogger("com.bjpowernode.jul.test.JULTest");
logger.info("输出info信息1");
logger.log(Level.INFO,"输出info信息2");


String name = "zs";
int age = 23;
// 拼接字符串方式
logger.log(Level.INFO,"学生的姓名为:"+name+";年龄为:"+age);

// 占位符方式
logger.log(Level.INFO,"学生的姓名:{0},年龄:{1}",new Object[]{name,age});

输出日志:

九月 12, 2021 10:16:33 上午 com.bjpowernode.jul.test.JULTest test01
信息: 输出info信息1
九月 12, 2021 10:16:33 上午 com.bjpowernode.jul.test.JULTest test01
信息: 输出info信息2
九月 12, 2021 10:16:33 上午 com.bjpowernode.jul.test.JULTest test01
信息: 学生的姓名为:zs;年龄为:23
九月 12, 2021 10:16:33 上午 com.bjpowernode.jul.test.JULTest test01
信息: 学生的姓名:zs,年龄:23

自定义 Logger 日志级别

//日志记录器
Logger logger = Logger.getLogger("com.bjpowernode.jul.test.JULTest");

//将默认的日志打印方式关闭掉
//参数设置为false,我们打印日志的方式就不会按照父logger默认的方式去进行操作
logger.setUseParentHandlers(false);

//处理器Handler
//在此我们使用的是控制台日志处理器,取得处理器对象
ConsoleHandler handler = new ConsoleHandler();
//创建日志格式化组件对象
SimpleFormatter formatter = new SimpleFormatter();

//在处理器中设置输出格式
handler.setFormatter(formatter);
//在记录器中添加处理器
logger.addHandler(handler);

//设置日志的打印级别
//此处必须将日志记录器和处理器的级别进行统一的设置,才会达到日志显示相应级别的效果
//logger.setLevel(Level.CONFIG);
//handler.setLevel(Level.CONFIG);

logger.setLevel(Level.ALL);
handler.setLevel(Level.ALL);

logger.severe("severe信息");
logger.warning("warning信息");
logger.info("info信息");
logger.config("config信息");
logger.fine("fine信息");
logger.finer("finer信息");
logger.finest("finest信息");

输出到日志文件

输出到文件使用 Level.ALL , 输出到控制台使用 Level.CONFIG

Logger logger = Logger.getLogger("com.bjpowernode.jul.test.JULTest");
logger.setUseParentHandlers(false);

//文件日志处理器
FileHandler handler = new FileHandler("D:\\myLogTest.log");
SimpleFormatter formatter = new SimpleFormatter();
handler.setFormatter(formatter);
logger.addHandler(handler);

//也可以同时在控制台和文件中进行打印
ConsoleHandler handler2 = new ConsoleHandler();
handler2.setFormatter(formatter);
logger.addHandler(handler2); //可以在记录器中同时添加多个处理器

logger.setLevel(Level.ALL);
handler.setLevel(Level.ALL);
handler2.setLevel(Level.CONFIG);

logger.severe("severe信息");
logger.warning("warning信息");
logger.info("info信息");
logger.config("config信息");
logger.fine("fine信息");
logger.finer("finer信息");
logger.finest("finest信息");

父子 Logger 日志打印

//父亲是RootLogger,名称默认是一个空的字符串
//RootLogger可以被称之为所有logger对象的顶层logger
Logger logger1 = Logger.getLogger("com.bjpowernode.jul.test");

Logger logger2 = Logger.getLogger("com.bjpowernode.jul.test.JULTest");

System.out.println(logger2.getParent() == logger1); //true
System.out.println("logger1的父Logger引用为:" + logger1.getParent() + "; 名称为" + logger1.getName() + "; 父亲的名称为" + logger1.getParent().getName());
System.out.println("logger2的父Logger引用为:" + logger2.getParent() + "; 名称为" + logger2.getName() + "; 父亲的名称为" + logger2.getParent().getName());
//父亲做设置
logger1.setUseParentHandlers(false);
ConsoleHandler handler = new ConsoleHandler();
SimpleFormatter formatter = new SimpleFormatter();
handler.setFormatter(formatter);
logger1.addHandler(handler);
handler.setLevel(Level.ALL);
logger1.setLevel(Level.ALL);

//儿子做打印
logger2.severe("severe信息");
logger2.warning("warning信息");
logger2.info("info信息");
logger2.config("config信息");
logger2.fine("fine信息");
logger2.finer("finer信息");
logger2.finest("finest信息");

Log4j

Maven 依赖

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

简介

Log4j 主要由 Loggers (日志记录器)、Appenders(输出控制器)和 Layout(日志格式化器)组成:

  • Loggers 控制日志的输出以及输出级别
  • Appenders 指定日志的输出方式(输出到控制台、文件等)
  • Layout 控制日志信息的输出格式

Loggers

日志记录器,负责收集处理日志记录,实例的命名就是类的全限定名,如 com.bjpowernode.log4j.XX , Logger的名字大小写敏感,其命名有继承机制:例如:name 为 com.bjpowernode.log4j 的 Logger会继承 name 为 com.bjpowernode 。 Log4j 中有一个特殊的 Logger 叫做 root ,他是所有 Logger 的根,也就意味着其他所有的 Logger 都会直接或者间接地继承自 root 。root Logger 可以用 Logger.getRootLogger() 方法获取。

关于日志级别信息,例如 DEBUG、INFO、WARN、ERROR … 日志级别是分大小的,DEBUG < INFO < WARN < ERROR,分别用来指定这条日志信息的重要程度

Log4j 输出日志的规则是:只输出级别不低于设定级别的日志信息,假设 Loggers 级别设定为 INFO,则 INFO、WARN、ERROR 级别的日志信息都会输出,而级别比 INFO 低的 DEBUG 则不会输出

Appenders

允许把日志输出到不同的地方,如控制台(Console)、文件(Files)等,可以根据天数或者文件大小产生新的文件,可以以流的形式发送到其它地方等等。

常用 Appenders :

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

Layouts

用户可以根据自己的喜好格式化自己的日志输出,Layouts 提供四种日志输出样式,如根据 HTML 样式、自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式。

常用 Layouts :

Layouts 描述
HTMLLayout 格式化日志输出为HTML表格形式
SimpleLayout 简单的日志输出格式化,打印的日志格式如默认 INFO 级别的消息
PatternLayout 最强大的格式化组件,可以根据自定义格式输出日志,如果没有指定转换格式, 就是用默认的转换格式

配置文件说明

配置文件路径:系统默认要从当前的类路径下找到 log4j.properties

配置文件有两项信息是必须要进行配置的:

static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
static final String APPENDER_PREFIX = "log4j.appender.";

log4j.rootLogger 的内容格式:

log4j.rootLogger=日志级别,appenderName1,appenderName2,appenderName3....

等价于 BasicConfigurator.configure();log4j.rootLogger 配置文件配置:

log4j.rootLogger=DEBUG,console

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern=%r [%t] %p %c %x - %m%n

配置文件占位符说明

使用 PatternLayout 可以自定义格式输出,是我们最常用的方式

这种格式化输出采用类似于 C 语言的 printf 函数的打印格式格式化日志信息,具体的占位符及其含义如下:

占位符 描述
%m 输出代码中指定的日志信息
%p 输出优先级,DEBUG、INFO 等
%n 换行符(Windows平台的换行符为 \n ,Unix 平台为 \n
%r 输出自应用启动到输出该 log 信息耗费的毫秒数
%c 输出打印语句所属的类的全名
%t 输出产生该日志的线程全名
%d 输出服务器当前时间,默认为 ISO 8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}
%l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)
%F 输出日志消息产生时所在的文件名称
%L 输出代码中的行号
%% 输出一个 % 字符

可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如:

占位符 描述
%5c 输出 category 名称,最小宽度是 5,category<5,默认的情况下右对齐
%-5c 输出 category 名称,最小宽度是 5,category<5,- 号指定左对齐,会有空格
%.5c 输出 category 名称,最大宽度是 5,category>5,就会将左边多出的字符截掉,<5 不会有空格
%20.30c category 名称 <20 补空格,并且右对齐,>30 字符,就从左边较远输出的字符截掉

使用说明

日志级别

Log4j 提供了 8 个级别的日志输出,分别为:

日志级别 描述
ALL 最低等级,用于打开所有级别的日志记录
TRACE 程序推进下的追踪信息,这个追踪信息的日志级别非常低,一般情况下是不会使用的
DEBUG 默认级别,指出细粒度信息事件对调试应用程序是非常有帮助的,主要是配合开发,在开发过程中打印一些重要的运行信息
INFO 消息的粗粒度级别运行信息
WARN 表示警告,程序在运行过程中会出现的有可能会发生的隐形的错误
注意,有些信息不是错误,但是这个级别的输出目的就是为了给程序员以提示
ERROR 系统的错误信息,发生的错误不影响系统的运行
一般情况下,如果不想输出太多的日志,则使用该级别即可
FATAL 表示严重错误,它是那种一旦发生系统就不可能继续运行的严重错误
如果这种级别的错误出现了,表示程序可以停止运行了
OFF 最高等级的级别,用户关闭所有的日志记录

打开 Log4j 日志系统日志

LogLog 会使用debug级别的输出为我们展现日志输出详细信息,Logger 是记录系统的日志,那么 LogLog 是用来记录 Logger 的日志

LogLog.setInternalDebugging(true);

输出日志:

log4j: Trying to find [log4j.xml] using context classloader sun.misc.Launcher$AppClassLoader@18b4aac2.
log4j: Trying to find [log4j.xml] using sun.misc.Launcher$AppClassLoader@18b4aac2 class loader.
log4j: Trying to find [log4j.xml] using ClassLoader.getSystemResource().
log4j: Trying to find [log4j.properties] using context classloader sun.misc.Launcher$AppClassLoader@18b4aac2.
log4j: Using URL [file:/E:/Study/%e6%95%99%e5%ad%a6%e8%a7%86%e9%a2%91/bilibili/Java%20%e6%97%a5%e5%bf%97%e6%a1%86%e6%9e%b6/%e6%ba%90%e7%a0%81/logProject01/LOG4J_STUDY/target/classes/log4j.properties] for automatic log4j configuration.
log4j: Reading configuration from URL file:/E:/Study/%e6%95%99%e5%ad%a6%e8%a7%86%e9%a2%91/bilibili/Java%20%e6%97%a5%e5%bf%97%e6%a1%86%e6%9e%b6/%e6%ba%90%e7%a0%81/logProject01/LOG4J_STUDY/target/classes/log4j.properties
log4j: Parsing for [root] with value=[DEBUG,console].
log4j: Level token is [DEBUG].
log4j: Category root set to DEBUG
log4j: Parsing appender named "console".
log4j: Parsing layout options for "console".
log4j: Setting property [conversionPattern] to [%r [%t] %p %c %x - %m%n].
log4j: End of parsing for "console".
log4j: Parsed "console" options.
log4j: Finished configuring.

FileAppender 输出日志文件

通过查看 org.apache.log4j.FileAppender 里的 set 方法,可以知道在配置文件中设置哪些属性:

  • fileAppend :表示是否追加信息,默认为 true
  • bufferSize :缓冲区的大小,默认为 8192
  • file :指定输出日志文件位置
  • encoding :指定输出日志文件的内容编码
#配置根节点logger
log4j.rootLogger=trace,file

#配置appender输出方式 输出到文件
log4j.appender.file=org.apache.log4j.FileAppender
#配置输出到文件中的格式
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
#第一个file是我们自己命名的appenderName,第二个file是用来指定文件位置的属性
log4j.appender.file.file=D://test//log4j.log
#配置输出字符编码
log4j.appender.file.encoding=UTF-8

RollingFileAppender 输出日志文件

RollingFileAppender 继承自 FileAppender ,可以使用 FileAppender 的属性设置,另外还有一些额外属性:

  • maxFileSize :表示拆分文件的大小
  • maxBackupIndex :表示拆分文件的数量,如果拆分文件不断增长,超过此数量,会按照时间来进行覆盖,原则就是保留新的,覆盖旧的
#RollingFileAppender的配置,我们可以针对于实际含义起名
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
log4j.appender.rollingFile.file=D://test//log4j.log
log4j.appender.rollingFile.encoding=UTF-8
#指定日志文件内容大小
log4j.appender.rollingFile.maxFileSize=1MB
#指定日志文件的数量
log4j.appender.rollingFile.maxBackupIndex=5

DailyRollingFileAppender 输出日志文件

按照时间来进行文件的拆分,继承自 FileAppender ,可以使用 FileAppender 的属性设置,另外还有一些额外属性:

  • datePattern :时间拆分策略,默认是按照天来进行拆分的
#DailyRollingFileAppender的配置,我们可以针对于实际含义起名
log4j.appender.dailyRollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
log4j.appender.dailyRollingFile.file=D://test//log4j.log
log4j.appender.dailyRollingFile.encoding=UTF-8
log4j.appender.dailyRollingFile.datePattern='.'yyyy-MM-dd HH-mm-ss

JDBCAppender 将日志持久化到数据库表中

#配置appender输出方式 输出到数据库表
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/test
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=123456
log4j.appender.logDB.Sql=INSERT INTO tbl_log(name,createTime,level,category,fileName,message) values('project_log','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%m')

自定义 Logger

我们以前所创建出来的 Logger 对象,默认都是继承 rootLogger 的,我们也可以自定义 Logger ,让其他 Logger 来继承这个 Logger ,这种继承关系就是按照包结构的关系来进行指定的

如果根节点的 Logger 和自定义父 Logger 配置的输出位置是不同的,则取二者的并集,配置的位置都会进行输出操作;如果二者配置的日志级别不同,以按照我们自定的父 Logger 的级别输出为主

我们之所以要自定义 Logger ,就是为了针对不同系统信息做更加灵活的输出操作

log4j.rootLogger=trace

#配置自定义logger
log4j.logger.com.bjpowernode.log4j.test=info,console

#针对 org.apache 包下的日志输出
log4j.logger.org.apache=error,console

实战案例

简单使用

//加载初始化配置
BasicConfigurator.configure();
Logger logger = Logger.getLogger(Log4jTest01.class);

logger.fatal("fatal信息");
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");

输出日志:

0 [main] FATAL com.bjpowernode.log4j.test.Log4jTest01  - fatal信息
1 [main] ERROR com.bjpowernode.log4j.test.Log4jTest01  - error信息
1 [main] WARN com.bjpowernode.log4j.test.Log4jTest01  - warn信息
1 [main] INFO com.bjpowernode.log4j.test.Log4jTest01  - info信息
1 [main] DEBUG com.bjpowernode.log4j.test.Log4jTest01  - debug信息

如果不执行加载初始化配置 BasicConfigurator.configure(); ,输出报错日志:

log4j:WARN No appenders could be found for logger (com.bjpowernode.log4j.test.Log4jTest01).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

JCL

Maven 依赖

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

简介

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

用户可以自由选择第三方的日志组件作为具体实现,像 Log4j ,或者 JDK 自带的 JUL , common-logging 会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。

当然,common-logging 内部有一个 Simple logger 的简单实现,但是功能很弱。所以使用 common-logging ,通常都是配合着 Log4j 以及其他日志框架来使用。

使用它的好处就是,代码依赖是 common-logging 而非 Log4j 的API, 避免了和具体的日志 API 直接耦合,在有必要时,可以更改日志实现的第三方库。

JCL 有两个基本的抽象类:

  • Log :日志记录器
  • LogFactory :日志工厂(负责创建 Log 实例)

JCL 组件结构

img

使用说明

JCL 使用原则: 如果有 Log4j ,优先使用 Log4j ,如果没有任何第三方日志框架的时候,我们使用的就是 JUL

Log 接口的 4 个实现类:

  • Log4JLogger :适配 Log4j
  • Jdk14Logger :适配 JDK 1.4 以后的 JUL
  • Jdk13LumberjackLogger :适配 JDK 1.4 以前的日志
  • SimpleLog :JCL 自带实现类

JCL 按照从上到下的顺序检测,如果检测到,就是用该实现

实战案例

简单使用

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


Log log = LogFactory.getLog(JCLTest01.class);
log.info("info信息");

SLF4J

Maven 依赖

<!-- slf4j 核心依赖 -->        
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

<!-- slf4j 桥接 JCL -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.25</version>
</dependency>

<!-- slf4j 桥接 Log4j --> 
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.25</version>
</dependency>

<!--slf4j 桥接 JUL-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.25</version>
</dependency>

<!-- slf4j NOP -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.25</version>
</dependency>

<!-- slf4j 自带简单日志实现 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.25</version>
</dependency>

<!-- slf4j + Logback ,无需桥接 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

日志门面

门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。

外观模式主要是体现了Java中的一种好的封装性。更简单的说,就是对外提供的接口要尽可能的简单。

常见的日志框架及日志门面

常见的日志实现:JUL、Log4j、Logback、Log4j2

常见的日志门面 :JCL、SLF4J

出现顺序 :Log4j --> JUL --> JCL --> SLF4J--> Logback--> Log4j2

SLF4J 简介

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

SLF4J 主要是为了给 Java 日志访问提供一套标准、规范的 API 框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如 Log4j 和 Logback 等。 当然 SLF4J 自己也提供了功能较为简单的实现,但是一般很少用到。

对于一般的 Java 项目而言,日志框架会选择 slf4j-api 作为门面,配上具体的实现框架(Log4j、Logback 等),中间使用桥接器完成桥接。所以我们可以得出 SLF4J 最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接。

SLF4J 桥接技术

SLF4J 桥接到其他具体日志实现

编码时 API 使用 SLF4J 的,具体日志输出使用其他桥接日志实现

例如:使用 SLF4J 的 API ,具体日志实现选择 Log4j ,Maven 中需要引入 slf4j-apislf4j-log4j12log4j

img

其他具体日志实现桥接到 SLF4J

编码时 API 使用具体日志实现,具体日志输出使用 SLF4J

例如:使用 Log4j 的 API ,具体日志实现由 SLF4J 选择 SLF4J Simple 实现,Maven 中需要引入 slf4j-apilog4j-over-slf4jslf4j-simple

注意:slf4j-log4j12log4j-over-slf4j 不能同时引入,否则会报错:

SLF4J: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. 
SLF4J: See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details.

java.lang.ExceptionInInitializerError

img

使用说明

日志级别

SLF4J 对日志的级别划分,分为 trace、debug、info、warn、error 五个级别:

  • trace :日志追踪信息
  • debug :日志详细信息
  • info :日志的关键信息 默认打印级别
  • warn :日志警告信息
  • error :日志错误信息

SLF4J 自动查找绑定实现

如果没有任何绑定实现,会提示错误:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

如果存在多个绑定实现,会选择 pom.xml 文件中从上到下选择第一个实现,并提示错误:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/80953/.m2/repository/org/slf4j/slf4j-nop/1.7.25/slf4j-nop-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/80953/.m2/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.helpers.NOPLoggerFactory]

slf4j-nop

使用 slf4j-nop ,表示不记录日志,需要单独导入 slf4j-nop 的 Maven 依赖

实战案例

简单使用

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


Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
logger.trace("trace信息");
logger.debug("debug信息");
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");

String name = "zs";
int age = 23;
// 使用占位符
logger.info("学生信息-姓名:{},年龄:{}",name,age);

// 输出异常日志
logger.error("具体错误是:",e);

Logback

Maven 依赖

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

<!-- logback日志实现 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

Logback 简介

Logback 是由 Log4j 创始人设计的又一个开源日志组件。

Logback 当前分成三个模块:logback-corelogback- classiclogback-access

  • logback-core 是其它两个模块的基础模块
  • logback-classic 是 Log4j 的一个改良版本。此外 logback-classic 完整实现 SLF4J API 。使你可以很方便地更换成其它日志系统如 Log4j 或 JDK14 Logging
  • logback-access 访问模块与 Servlet 容器集成提供通过 Http 来访问日志的功能

Logback 中的组件

  • Logger : 日志的记录器,主要用于存放日志对象,也可以定义日志类型、级别
  • Appender :用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等
  • Layout :负责把事件转换成字符串,格式化的日志信息的输出。在 Logback 中 Layout 对象被封装在 encoder 中。也就是说我们未来使用的 encoder 其实就是 Layout

Logback 配置文件

Logback 提供了 3 种配置文件:

  • logback.groovy
  • logback-test.xml
  • logback.xml

如果都不存在则采用默认的配置

配置文件中的占位符说明

占位符 描述
%-10level 日志级别,案例为设置 10 个字符,左对齐
%d{yyyy-MM-dd HH:mm:ss.SSS} 日期
%c 当前类全限定名
%M 当前执行日志的方法
%L 打印日志代码行号
%thread 当前线程名称
%m 或者 %msg 日志信息
%n 换行

使用说明

一般使用 SLF4J + Logback ,编码时使用 SLF4J 的 API

Logback 的日志级别同 SLF4J ,默认为 DEBUG 级别

配置文件示例

简单使用

配置根 Logger 日志级别为 ALL ,关联控制台日志输出 Appender :

<?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="consoleAppender" 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>

    <!--
        日志记录器
        配置root logger
        level:配置日志级别

        可以同时配置多个appender,做日志的多方向输出
    -->
    <root level="ALL">
        <!-- 引入appender -->
        <appender-ref ref="consoleAppender"/>
    </root>
</configuration>

FileAppender 日志文件输出

<!-- 配置文件的输出路径 -->
<property name="logDir" value="D://test"></property>

<!-- 配置文件的appender 普通文件-->
<appender name="fileAppender" class="ch.qos.logback.core.FileAppender">

    <!-- 引入文件位置 -->
    <file>${logDir}/logback.log</file>

    <!-- 设置输出格式 -->
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <pattern>${pattern}</pattern>
    </encoder>

</appender>

输出为 HTML 文件:

<!-- 配置文件的appender html文件 -->
<appender name="htmlFileAppender" class="ch.qos.logback.core.FileAppender">

    <file>${logDir}/logback.html</file>
    
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="ch.qos.logback.classic.html.HTMLLayout">
            <pattern>${pattern1}</pattern>
        </layout>
    </encoder>

</appender>

RollingFileAppender 拆分输出日志文件

<!-- 配置文件的appender 可拆分归档的文件 -->
<appender name="roll" class="ch.qos.logback.core.rolling.RollingFileAppender">

    <!-- 输入格式 -->
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <pattern>${pattern}</pattern>
    </encoder>
    
    <!-- 引入文件位置 -->
    <file>${logDir}/roll_logback.log</file>

    <!-- 指定拆分规则 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <!-- 按照时间和压缩格式声明文件名 压缩格式gz -->
        <fileNamePattern>${logDir}/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>

        <!-- 按照文件大小来进行拆分 -->
        <maxFileSize>1KB</maxFileSize>
    </rollingPolicy>

</appender>

AsyncAppender 异步输出日志

<!-- 配置异步日志 -->
<appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="consoleAppender"/>
</appender>

自定义日志级别

<!--
	additivity="false" : 表示不继承 rootlogger
-->
<logger name="com.bjpowernode" level="info" additivity="false">
    <!-- 在自定义logger中配置appender -->
    <appender-ref ref="consoleAppender"/>
</logger>

实战案例

简单使用

Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);

logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");

Log4j2

Maven 依赖

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

<!-- log4j适配器 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.12.1</version>
</dependency>

<!-- log4j2日志门面 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.12.1</version>
</dependency>

<!-- log4j2日志实现 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.12.1</version>
</dependency>

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

Log4j2 简介

Apache Log4j2 是对 Log4j 的升级,它比其前身 Log4j 1.x 提供了重大改进,并提供了 Logback 中可用的许多改进,同时修复了 Logback 架构中的一些问题。被誉为是目前最优秀的 Java 日志框架。

Log4j2 特征

  • 性能提升 : Log4j2 包含基于 LMAX Disruptor 库的下一代异步记录器。在多线程场景中,异步记录器的吞吐量比 Log4j 1.x 和 Logback 高 18 倍,延迟低

  • 自动重新加载配置 : 与 Logback 一样,Log4j2 可以在修改时自动重新加载其配置。与 Logback 不同,它会在重新配置发生时不会丢失日志事件

  • 高级过滤 : 与 Logback 一样,Log4j2 支持基于 Log 事件中的上下文数据,标记,正则表达式和其他组件进行过滤。此外,过滤器还可以与记录器关联。与 Logback 不同,Log4j2 可以在任何这些情况下使用通用的 Filter 类

  • 插件架构 : Log4j2 使用插件模式配置组件。因此,您无需编写代码来创建和配置 Appender,Layout,Pattern Converter等。在配置了的情况下,Log4j2 自动识别插件并使用它们

  • 无垃圾机制 : 在稳态日志记录期间,Log4j2 在独立应用程序中是无垃圾的,在 Web 应用程序中是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的响应性能。

目前市面上最主流的日志门面就是 SLF4J ,虽然 Log4j2 也是日志门面,因为它的日志实现功能非常强大,性能优越。所以我们一般情况下还是将 Log4j2 看作是日志的实现

SLF4j + Log4j2 的组合,是市场上最强大的日志功能实现方式,绝对是未来的主流趋势。

异步日志

异步日志是 Log4j2 最大的特色,其性能的提升主要也是从异步日志中受益。

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

注意这是两种不同的实现方式,在设计和源码上都是不同的体现。

AsyncAppender 方式

是通过引用别的 Appender 来实现的,当有日志事件到达时,会开启另外一个线程来处理它们。需要注意的是,如果在 Appender 的时候出现异常,对应用来说是无法感知的。

AsyncAppender 应该在它引用的 Appender 之后配置,默认使用 java.util.concurrent.ArrayBlockingQueue 实现而不需要其它外部的类库。 当使用此 Appender 的时候,在多线程的环境下需要注意,阻塞队列容易受到锁争用的影响,这可能会对性能产生影响。这时候,我们应该考虑使用无锁的异步记录器(AsyncLogger)。

AsyncLogger 方式

AsyncLogger 才是 Log4j2 实现异步最重要的功能体现,也是官方推荐的异步方式。

它可以使得调用 Logger.log 返回的更快。你可以有两种选择:全局异步和混合异步:

  • 全局异步 :所有的日志都异步的记录,在配置文件上不用做任何改动,只需要在 jvm 启动的时候增加一个参数即可实现
  • 混合异步 :你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。虽然 Log4j2 提供了一套异常处理机制,可以覆盖大部分的状态,但是还是会有一小部分的特殊情况是无法完全处理的,比如我们如果是记录审计日志(特殊情况之一),那么官方就推荐使用同步日志的方式,而对于其他的一些仅仅是记录一个程序日志的地方,使用异步日志将大幅提升性能,减少对应用本身的影响。混合异步的方式需要通过修改配置文件来实现,使用 AsyncLogger 标记配置

配置文件说明

默认使用 log4j2.xml ,默认日志级别 ERROR

简单配置文件

配置根 Logger 日志级别为 trace ,使用控制台输出日志

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <!-- 配置全局通用属性 -->
    <properties>
        <property name="logDir">D://test</property>
    </properties>
    
    <!-- 配置appender -->
    <Appenders>
        <!-- 配置控制台输出 -->
        <Console name="consoleAppender" target="SYSTEM_OUT">

        </Console>
    </Appenders>
    
    <!-- 配置logger -->
    <Loggers>
        <!-- 配置rootlogger -->
        <Root level="trace">
            <!-- 引用Appender -->
            <AppenderRef ref="consoleAppender"/>
        </Root>
    </Loggers>
</Configuration>

配置日志文件输出

<!-- 配置appender -->
<Appenders>
    <!-- 配置文件输出-->
    <File name="fileAppender" fileName="${logDir}//log4j2.log">
        <!-- 配置文件输出格式 -->
        <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
    </File>
</Appenders>

配置可拆分日志文件输出

<!-- 配置appender -->
<Appenders>        
<!--
	按照指定规则来拆分日志文件
		fileName:日志文件的名字
		filePattern:日志文件拆分后文件的命名规则
			$${date:yyyy-MM-dd}:根据日期当天,创建一个文件夹,例如:2021-01-01这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中);2021-01-02这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)

		rollog-%d{yyyy-MM-dd-HH-mm}-%i.log
			为文件命名的规则:%i表示序号,从0开始,目的是为了让每一份文件名字不会重复
-->
    <RollingFile name="rollingFile" fileName="${logDir}/rollog.log" filePattern="${logDir}/$${date:yyyy-MM-dd}/rollog-%d{yyyy-MM-dd-HH-mm}-%i.log">

        <!-- 日志消息格式 -->
        <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>

        <Policies>
            <!-- 在系统启动时,触发拆分规则,产生一个日志文件 -->
            <OnStartupTriggeringPolicy/>

            <!-- 按照文件的大小进行拆分 -->
            <SizeBasedTriggeringPolicy size="10KB"/>

            <!-- 按照时间节点进行拆分 拆分规则就是filePattern-->
            <TimeBasedTriggeringPolicy/>
        </Policies>

        <!-- 在同一目录下,文件的个数限制,如果超出了设置的数值,则根据时间进行覆盖,新的覆盖旧的规则-->
        <DefaultRolloverStrategy max="30"/>

    </RollingFile>
</Appenders>

配置异步日志

AsyncAppender 方式
<!-- 配置appender -->
<Appenders>          
	<!-- 配置异步日志 -->
    <Async name="myAsync">
        <!-- 将控制台输出做异步的操作 -->
        <AppenderRef ref="consoleAppender"/>
    </Async>
</Appenders>
AsyncLogger 方式
<!-- 配置logger -->
<Loggers>

    <!-- 自定义logger,让自定义的logger为异步logger -->
    <!--
            includeLocation="false"
            	表示去除日志记录中的行号信息,这个行号信息非常的影响日志记录的效率(生产中都不加这个行号)
            	严重的时候可能记录的比同步的日志效率还有低

            additivity="false"
            	表示不继承rootlogger
        -->
    <AsyncLogger name="com.bjpowernode" level="trace"
                 includeLocation="false" additivity="false">
        <!-- 将控制台输出consoleAppender,设置为异步打印 -->
        <AppenderRef ref="consoleAppender"/>
    </AsyncLogger>
</Loggers>

Spring Boot 日志实现

SpringBoot 是现今市场上最火爆用来简化 Spring 开发的框架,SpringBoot 日志也是开发中常用的日志系统。

SpringBoot 默认就是使用 SLF4J 作为日志门面,Logback 作为日志实现来记录日志。

使用 Log4j2

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!-- 排除掉原始依赖 以此去除logback引用 -->
        <exclusion>
            <artifactId>spring-boot-starter-logging</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 添加log4j2依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

Spring Boot 与日子相关的配置

# 自定义 Logger 日志级别
logging.level.com.bjpowernode=trace
# 指定控制台输出的日志格式
logging.pattern.console=%d{yyyy-MM-dd} [%level] -%m%n
# 指定日志输出文件路径
logging.file.path=D:/test/springbootlog

参考资料

posted @ 2021-09-16 09:39  流星<。)#)))≦  阅读(157)  评论(0编辑  收藏  举报