SLF4j的介绍与使用+SpringBoot日志配置
关于日志
日志级别
fatal > error > warn > info > debug > trace
- trace:级别最低
- debug:需要调试时候的关键信息
- info:普通的打印信息
- warn:警告信息
- error:错误信息
- fatal:灾难级的,因为代码异常导致程序退出执行的事件;系统级别,程序无法打印
当某个目录设置了日志级别,我们只能得到此级别及更高级别的日志。
springboot默认日志级别是 info
,我们可以在springboot的默认配置文件中修改日志级别
日志格式
日志框架
日志框架包括日志门面、日志实现。日志门面就是接口,日志实现相当于是具体的实现类
一般常用的框架有:
JUL,JCL,JBOSS-LOGING,longback,log4j,log4j2,slf4j等等。
日志门面 (日志的抽象层) | 日志实现 |
---|---|
Log4j JUL(java.util.logging) Log4j2 Logback |
左边选一个门面(抽象层)、右边来选一个实现。SpringBoot选用的是SLF4J和logback。
关于SLF4j
介绍
slf4j,simple logging facade for java的缩写,翻译为java的简单日志外观。slf4j(simple logging facade for java)不是一个真正的日志实现,而是一个抽象层( abstraction layer),也可以理解为一个接口,它是一种适配器的实现方式,它本身不具有输出日志的功能,输出日志还是由log4j、logback等这样的日志组件来进行输出。
简单来说,它仅仅是一个为Java程序提供日志输出的统一接口,并不是一个具体的日志实现方案,就比如JDBC一样,只是一种规则而已。所以单独的slf4j是不能工作的,必须搭配其他具体的日志实现方案。
使用场景
现在开发项目都是使用maven进行构建开发,假设架构师a开发了一个order.jar通用组件,他在程序中使用的是log4j组件进行日志输出;程序员b自己之前一直在开发自己的业务模块,并且他在程序中使用的是logback日志组件,突然有一天程序员b需要在自己的业务系统中使用架构师a的order.jar通用组件,这个时候问题就出现了,由于两套程序使用了不同的日志组件,程序员b除了要维护自己的logback日志组件配置,还需要维护order,jar中的日志组件配置,这个问题是很头疼的。解决方案就是:使用slf4j。
SLF4j的使用
使用slf4j,只有一个强制性的依赖,就是slf4j-api-x.x.x.jar,我们在编写代码的时候,只会使用这个jar包里的API,应用程序在运行时去类路径下查找绑定的具体日志框架,并使用该绑定的日志框架进行实际的日志操作,如果在应用程序的类路径下面没有找到合适的绑定的话,slf4j默认使用一个没有任何操作的实现。
slf4j只是一个日志标准,并不是日志系统的具体实现。理解这句话非常重要,slf4j只做两件事情:
- 提供日志接口
- 提供获取具体日志对象的方法
比如slf4j-simple、logback都是slf4j的具体实现。
当一个应用面向了很多日志框架,如果我们直接去掉这些依赖包的话,系统肯定会报错的,因为Spring本来底层会调用这些框架的API等。可以采取偷梁换柱的做法:用另一个jar包代替本来的jar包。比如:log4j12;代替后和slf4j完美契合,就可以使用。
引入Maven依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
总结:
- 先将以前的剔除
- 用中间包替换原有的日志框架
- 导入slf4j其他的实现
slf4j-api作为slf4j的接口类,使用在程序代码中,这个包提供了一个Logger类和LoggerFactory类,Logger类用来打日志,LoggerFactory类用来获取Logger,而slf4j-log4j就是连接slf4j和log4j的桥梁。那么他们是怎么连接的呢?我们来看看slf4j的LoggerFactory类的getLogger函数的源码:
/**
* Return a logger named corresponding to the class passed as parameter, using
* the statically bound {@link ILoggerFactory} instance.
*
* @param clazz the returned logger will be named after clazz
* @return logger
*/
public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}
/**
* Return a logger named according to the name parameter using the statically
* bound {@link ILoggerFactory} instance.
*
* @param name The name of the logger.
* @return logger
*/
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();
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
return TEMP_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
LoggerFactory.getLogger()首先获取一个ILoggerFactory接口,然后使用该接口获取具体的Logger。获取ILoggerFactory的时候用到了一个StaticLoggerBinder类,仔细研究我们会发现StaticLoggerBinder这个类并不是slf4j-api这个包中的类,而是slf4j-log4j包中的类,这个类就是一个中间类,它用来将抽象的slf4j变成具体的log4j,也就是说具体要使用什么样的日志实现方案,就得靠这个StaticLoggerBinder类。再看看slf4j-log4j包种的这个StaticLoggerBinder类创建ILoggerFactory长什么样子:
private final ILoggerFactory loggerFactory;
private StaticLoggerBinder() {
loggerFactory = new Log4jLoggerFactory();
try {
Level level = Level.TRACE;
} catch (NoSuchFieldError nsfe) {
Util
.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
}
}
public ILoggerFactory getLoggerFactory() {
return loggerFactory;
}
可以看到slf4j-log4j中的StaticLoggerBinder类创建的ILoggerFactory其实是一个org.slf4j.impl.Log4jLoggerFactory,这个类的getLogger函数是这样的:
public Logger getLogger(String name) {
Logger slf4jLogger = loggerMap.get(name);
if (slf4jLogger != null) {
return slf4jLogger;
} else {
org.apache.log4j.Logger log4jLogger;
if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))
log4jLogger = LogManager.getRootLogger();
else
log4jLogger = LogManager.getLogger(name);
Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
return oldInstance == null ? newInstance : oldInstance;
}
}
就在其中创建了真正的org.apache.log4j.Logger,也就是我们需要的具体的日志实现方案的Logger类。就这样,整个绑定过程就完成了。
在实际开发中,建议使用slf4j,而不是直接使用 log4j, commons logging, logback 或者 java.util.logging等,因为这样可以让程序具有更多的扩展性。
- **与客户端解耦:**使用slf4j会使得它独立于任何一个特定的日志实现,这意味着不需要管理多个日志配置或者多个日志类库,以后别人调用你的工具包时也可以不用关心日志组件问题。
- **省内存:**slf4j提供了基于占位符
{}
的日志方法,不会有字符串拼接操作,减少了无用String对象的数量,节省了内存。并且,使用slf4j的日志方法,可以延迟构建日志信息(srting)的开销,程序可以有更高的吞吐性能。
SpringBoot日志配置
默认日志
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
依赖关系图:
- 我们可以看到SpringBoot底层使用slf4j+logback的方式进行日志记录
- 其他日志会转换成slf4j
- 可以从依赖中看到明显的替换包
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可。
基本使用配置
logging:
#logging.level是map类型, 我们可以指定不同包下使用不同的日志级别
level:
com.atguigu: trace
#以文件形式打印日志logging.file
file:
name: boot.log #不指定日志文件的具体位置, 只指定文件名字时, 日志文件默认会生成在项目目录下
name: D:/boot.log #指定日志文件的具体位置时, 日志文件会生成在指定的位置
path: /spring/log #在项目所在磁盘根目录下的spring目录下的log目录下生成名为spring.log的日志文件
#日志输出格式:控制台 or 文件
pattern:
console: %d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
file: %d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n
# 日志输出格式:
# %d 表示日期时间,
# %thread 表示线程名,
# %-5level 级别从左显示5个字符宽度
# %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
# %msg 日志消息,
# %n 换行符
#-->
# %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
指定日志配置文件
在类路径下放上每个日志框架自己的配置文件即可, springboot就不使用他默认的日志配置了
Logging System | Customization |
---|---|
Logback | logback-spring.xml , logback-spring.groovy , logback.xml , or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
当日志配置命名为logback.xml
时, 这个配置直接就被日志框架识别了
当日志配置命名为logback-spring.xml
时, 这个配置由springboot解析识别, 可以使用更高级的功能, 例如 springProfile
, 他与springboot中的Profiles
对应, 在springboot默认配置文件中激活指定Profiles, 日志配置也将改变
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>
如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误:
no applicable action for [springProfile]
比如在logback-spring.xml中做出以下配置,就可以指定某段配置只在某个环境下生效
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="log.path" value="./logs" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>[%X{trackId}][%thread] [%d{yyyy-MM-dd HH:mm:ss.SSS}] %-5level %logger{30} - %msg%n</pattern>
</layout>
</appender>
<!-- level为 info 日志,时间滚动输出 -->
<appender name="Log_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${log.path}/info.log</file>
<!--日志文档输出格式-->
<encoder>
<pattern>[%X{trackId}][%thread] [%d{yyyy-MM-dd HH:mm:ss.SSS}] %-5level %logger{30} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志归档 -->
<fileNamePattern>${log.path}/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!--日志文档保留天数-->
<maxHistory>5</maxHistory>
<maxFileSize>100MB</maxFileSize>
<totalSizeCap>5GB</totalSizeCap>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
</appender>
<springProfile name="dev">
<root level="info">
<appender-ref ref="CONSOLE" />
</root>
<!-- mybatis sql单独控制输出级别 -->
<logger name="com.demo.example.dao" level="debug"/>
</springProfile>
<springProfile name="prod">
<root level="info">
<appender-ref ref="Log_FILE" />
</root>
<!-- mybatis sql单独控制输出级别 -->
<logger name="com.demo.example.dao" level="debug"/>
</springProfile>
<springProfile name="online">
<root level="info">
<appender-ref ref="Log_FILE" />
</root>
<!-- mybatis sql单独控制输出级别 -->
<logger name="com.demo.example.dao" level="debug"/>
</springProfile>
</configuration>
切换日志框架
-
springboot默认使用
spring-boot-starter-logging
启动器, 使用这个启动器默认使用Logback
进行日志记录, 如果要使用Log4j2
进行日志记录, 那么可以切换spring-boot-starter-log4j2
启动器 -
具体切换方法为, 将默认
spring-boot-starter-logging
启动器排除, 使用spring-boot-starter-log4j2
启动器<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!--排除默认spring-boot-starter-logging启动器--> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!--使用spring-boot-starter-log4j2启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
-
切换log4j日志框架
首先排除日志框架的实现jar, 比如偷梁换柱jar, log4j-to-slf4j.jar, 和logback-classic.jar(logback实现jar), 然后引入log4j的实现jar
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency>
更多推荐
所有评论(0)