slf4j
slf4j - simple log facade for java
门面模式
slf4j是门面模式的典型应用
门面模式的核心是“外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用”
门面模式的核心为Facade即门面对象,门面对象核心:
- 知道所有子角色的功能和责任
- 将客户端发来的请求委派到子系统中,没有实际业务逻辑
-
为什么要使用slf4j
举个栗子
我们的系统使用了logback这个日志系统,我们的系统又使用了A.jar,A.jar中使用的日志系统为log4j,我们的系统又使用了B.jar,B.jar中使用的日志系统为slf4j-simple,这样我们的系统就必须同时支持和维护logback/log4j/slf4j-simple三种日志框架,非常不便。
为了解决上述的问题,可以通过引入一个适配层,由适配层决定使用哪一种日志系统,而调用端只用打印日志即可,不需要关心如何打印日志,slf4j就是这种适配层。
slf4j只是一个日至标准,并不是日志系统的具体实现,只做一下两件事情: 提供日志接口
- 提供获取具体日志对象的方法
slf4j-simple、logback都是slf4j的具体实现,log4j并不直接实现slf4j,但是有专门的一层桥接slf4j-log4j12来实现slf4j。
slf4j好处
- 解耦合日志和项目
slf4j是一个适配器,我们通过slf4j的日志方法统一打印日志时,可以忽略其他日志的具体方法,当系统切换日志源时不需要更改代码。 - 节省内存
log4j这些传统的日志系统里面没有占位符的概念,需要打印信息的时候,不得不创建无用String对象来进行输出信息的拼接。在项目中使用slf4j
1. 导入相关依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.13</version>
</dependency>
<!-- 引入log4j日志依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
2. 创建Logger对象
```xml import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
/**
- @author liuwenxiu
- @Description:
@date 2022-7-6 18:39 */ public class LogTest { private static final Logger logger = LoggerFactory.getLogger(LogTest.class);
@Test public void testLog() {
logger.error("错误信息为:{}","Something error happen...");
}
}
> 注意点:项目中的Logger对象可能不止一个,注意在导包的时候导入**org.slf4j的Logger对象**
<a name="zRHHf"></a>
#### 对象修饰符注意点
在创建对象时一般会使用**private static final**来修饰日志对象,原因如下
- private => 防止其他类使用当前类的日志对象
- static => 让每个类中的日志对象只生成一份,日志对象是属于类的,不属于具体的实例
- final => 避免日志对象在运行时被修改
<a name="TSjo1"></a>
## slf4j实现原理
```java
Logger logger = LoggerFactory.getLogger(LogTest.class);
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
//...省略部分代码的展示
return logger;
}
// 此步骤就是根据Class对象获取其全限定名然后调用下面的方法
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
//...省略部分代码的展示
}
/*
if(初始化状态 = 未初始化) {
初始化状态 = 正在进行初始化;
执行初始化();
}
*/
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
private static Set<URL> findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order during iteration
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
// 核心代码是第11行
// 在getLogger时会去classpath下找STATIC_LOGGER_BINDER_PATH
// STATIC_LOGGER_BINDER_PATH的值是org/slf4j/impl/StaticLoggerBinder.class
// 即所有slf4j的实现在提供的jar包路径下都存在上述的这个类
// 我们不能避免在系统中同时引入多个slf4j的实现,所以接收的地方是一个Set。
Log4j
Log4J 是 Apache 的一个开源项目(官网 http://jakarta.apache.org/log4j),通过在项目中使用 Log4J,我们可以控制日志信息输出到控制台、文件、GUI 组件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以更灵活的控制日志的输出过程,方便项目的调试。
Log4j的组成
- Loggers(日志记录器) - 控制日志的输出级别与日志是否输出
- Appenders(输出端) - 指定日志的输出方式,输出到控制台、文件等
- Layout(日志格式化器) - 控制日志信息的输出格式
日志级别
| 日志级别 | 含义 | | —- | —- | | OFF | 最高日志级别,关闭左右日志 | | FATAL | 将会导致应用程序退出的错误 | | ERROR | 发生错误事件,仍不影响系统的继续运行 | | WARN | 警告,即潜在的错误情形 | | INFO | 一般用于粗粒度级别上,强调应用程序的运行全程 | | DEBUG | 一般用于细粒度级别上,对调试应用程序非常有帮助 | | ALL | 最低等级,打开所有日志记录 |
注:一般只使用4个级别,优先级从高到低为 ERROR > WARN > INFO > DEBUG
Appender
输出端类型 | 作用 |
---|---|
ConsoleAppender | 将日志输出到控制台 |
FileAppender | 将日志输出到文件中 |
DailyRollingFileAppender | 将日志输出到一个日志文件,并且每天输出到一个新的文件 |
RollingFileAppender | 将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件 |
JDBCAppender | 把日志信息保存到数据库中 |
Layout
格式化器类型 | 作用 |
---|---|
HTMLLayout | 格式化日志输出为HTML表格形式 |
SimpleLayout | 简单的日志输出格式化,打印的日志为(info-message) |
PatternLayout | 最强大的格式化日期,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式。 |
slf4j和log4j的区别
- log4j(log for java)
Apache的一个开源项目,可以灵活地记录日志信息,通过log4j的配置文件灵活配置日志的记录格式、记录级别、输出格式,而不需要修改已有的日志记录代码。 - slf4j(simple log facade for java)
slf4j不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,slf4j是一个用于日志系统的简单Facade,允许用户在部署其应用时所希望的日志系统。
可以将log4j看做一个完整的日志库,而slf4j是一个日志库的规范接口。
log4j2的配置文件
基础信息
配置文件的格式:xml形式/json格式
配置文件的位置:log4j2默认会在classpath目录下寻找log4j2.xml、log4j2.json、log4j.jsn等名称的文件
系统选择配置文件的优先级从先到后如下所示:
- log4j2-test.json或者log4j2-test.jsn文件
- log4j2-test.xml文件
- log4j2.json或log4j2.jsn文件
- log4j2.xml文件
一般在开发时默认使用log4j2.xml进行命名
配置示例
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="WARN" monitorInterval="30">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
配置信息
Configuration节点
子节点:Appenders和Loggers
属性:status和monitorinterval
- status:指定log4j本身打印日志的级别
monitorinterval:指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s
Appenders节点
Console节点
子节点:ThresholdFilter、PatternLayout
属性:name、targetname:指定Appender的名字
target:SYSTEM_OUT或SYSTAEM_ERR,一般只设置默认SYSTEM_OUT
ThresholdFilter节点
临界值过滤器,过滤掉低于指定临界值的日志
属性:onMatch、onMismatchonMatch:表示匹配设定的日志级别后是DENY还是ACCEPT
onMismatch:表示不匹配设定的日志级别是DENY、ACCEPT还是NEUTRAL
PatternLayout节点
属性:charset、pattern
charset:设置语言,常用”UTF-8”
-
File节点
定义输出到指定位置文件的Appender
子节点:PatternLayout
属性:name、fileName -
RollingFile节点
定义超过指定大小自动删除旧的创建新的Appender
子节点:ThresholdFilter、PatternLayout、Policies、DefaultRolloverStrategy
属性:name、fileName、filePattern -
Policies节点
子节点:TimeBasedTriggeringPolicy、SizeBasedTriggeringPolicy
TimeBasedTriggeringPolicy:基于时间的滚动策略
interval属性用来指定多久滚动一次,默认是1小时
modulate=true用来调整时间:比如现在是3:00am,interval是4,那么第一次滚动在4:00am,接着是8:00am,12:00am…而不是7:00amSizeBasedTriggeringPolicy:基于指定文件大小的滚动策略
size属性用来指定每个日志文件的大小DefaultRolloverStrategy节点
指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)
Loggers节点
Root节点
指定项目的根日志,如果没有单独指定Logger,默认使用该Root日志输出
level:日志输出级别,共有八个级别,从低到高为:ALLLogger节点
单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等
属性:level、namename:用来指定该Logger所适用的类或者类所在的包的全路径,继承自Root节点
子节点:AppenderRef
- 指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root。如果指定了,会在指定的这个Appender和Root的Appender中都会输出。如果设置Logger的additivity=”false”则只会在自定义的Appender中输出。
配置参数详解
%d{HH:mm:ss.SSS} 表示输出到毫秒的时间
%t 输出当前线程名称
%-5level 输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0
%logger 输出logger名称,因为Root Logger没有名称,所以没有输出
%msg 日志文本
%n 换行
其他常用的占位符有:
%F 输出所在的类文件名,如Log4j2Test.java
%L 输出行号
%M 输出所在方法名
%l 输出语句所在的行数, 包括类名、方法名、文件名、行数日志Level
| 级别 | 含义 | | —- | —- | | ALL | 最低级别,用于打开所有的日志记录 | | Trace | 追踪,程序推进下可以写个trace输出 | | Debug | 一般用于细粒度级别上,对调试应用程序非常有帮助 | | Info | 一般用于粗粒度级别上,强调应用程序的运行全程 | | Warn | 输出警告及warn以下级别的日志 | | Error | 输出错误信息日志 | | Fatal | 输出每个严重的错误事件将会导致应用程序退出的日志 | | OFF | 最高级别,关闭所有日志记录 |