还不知道项目中怎么写日志?slf4j+log4j帮你搞定!
前言
之前讲到了排查问题最重要的两种方法:查日志、debug。断点调试在上一期讲了,这期就讲讲日志。本文将从Log4j入手,分别介绍slf4j、Log4j2以及SpringBoot中的日志使用。
(一)Log4j
Log4j主要由Loggers(日志记录器)、Appenders(输出端)和Layout(日志格式化器)组成,其中Loggers控制日志输出的级别与日志是否输出;Appenders指定日志的输出方式(输出到控制台或者文件);Layout控制日志信息的输出格式。
1.1 引入依赖
首先引入Log4j的相关依赖:
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
1.2 使用
在不自定义配置文件的情况下,可以通过BasicConfigurator.configure();设置一个基本的配置,Log4j的日志共有六种级别,主要使用的是中间的四种:
//如果没有自定义配置信息的话加上下面的代码
BasicConfigurator.configure();
Logger logger=Logger.getLogger(Log4jTest.class);
//logger日志级别
logger.fatal("fatal"); //严重错误,一般会造成系统崩溃并终止运行
logger.error("error"); //错误信息,不会影响系统运行
logger.warn("warn"); //警告信息,可能会发生问题
logger.info("info"); //运行信息,比如数据库连接、网络连接等
logger.debug("debug"); //调试信息,一般用在开发过程中打印调试信息
logger.trace("trace"); //追踪信息,记录程序所有的流程信息
1.3 自定义配置信息
通过分析LoggerManager的源码可以发现,Log4j会去查找log4j.xml和log4j.properties文件中的配置信息。因此我们自定义配置文件时也需要创建这两个文件中的其中一个,现在广泛使用log4j.properties
接下来简单配置一下:
Logger控制日志输出的级别与日志是否输出,通过rootLogger可以设置输出的最低级别。
appender指定日志的输出方式,这里选择输出到控制台
layout控制日志信息的输出格式,这里使用简单格式
log4j.rootLogger=info,console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.SimpleLayout
(二)Log4j日志配置文件详解
接下来详细介绍Log4j配置文件:
# 指定RootLogger顶级父元素默认的配置信息,指定日志级别=info,使用的append为console(自己命名)
log4j.rootLogger = info,console
#指定控制台日志输出的appender,这里名字要和上面填入的一致console
log4j.appender.console=org.apache.log4j.ConsoleAppender
#指定消息格式layout
log4j.appender.console.layout=org.apache.log4j.SimpleLayout
2.1 常用的Layout
HTMLLayout:格式化日志输出为HTML表格样式
SimpleLayout:简单的日志输出格式
PatternLayout:可以根据自定义格式输出日志,如果没有指定转换格式,就是采用默认的格式
重点介绍最常用的PatternLayout
使用方式:
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern= [%p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
conversionPattern是格式,上面的这个格式化可以直接拿过去用,其他参数配置如下:
%m 输出代码中指定的日志信息
%p 输出优先级
%n 换行符
%r 输出应用启动到该log信息耗费的毫秒数
%c 输出打印语句所属的类的全名
%t 输出产生该日志的线程全名
%d 输出服务器当前时间,默认ISO8601,也可以指定格式如%d{yyyy-MM-dd HH:mm:ss.SSS}
%l 输出日志时间发生的位置,包括类名、线程及在代码钟的行数
%F 输出日志消息产生时所在的文件名称
%L 输出代码中的行号
%% 输出一个%符号
2.2 常用的appender
ConsoleAppender:控制台输出
FileAppender:文件中输出
JDBCAppender:数据库中输出
2.2.1 FileAppender
介绍FileAppender文件输出的一些自定义配置
#将file也配置到rootLogger中
log4j.rootLogger=info,console,file
#省略console的配置
#指定appender以文件形式输出
log4j.appender.file=org.apache.log4j.FileAppender
#指定文件的名称和路径
log4j.appender.file.file=/logs/log4j.log
#指定日志文件的字符集
log4j.appender.file.encoding=UTF-8
#日志格式
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.conversionPattern= [%p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
我们还可以根据业务的不同,使用RollingFIleAppender或者DailyRollingFileAppenderRollingFIleAppender按照文件大小拆分,设置文件内容最大值以及文件数量,如果都超过则覆盖之前的。
#按照文件大小拆分
#指定appender以文件形式输出
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
#指定文件的名称和路径
log4j.appender.rollingFile.file=/logs/log4j.log
#指定日志文件的字符集
log4j.appender.rollingFile.encoding=UTF-8
#日志格式
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.conversionPattern= [%p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
#指定日志文件内容的大小
log4j.appender.rollingFile.maxFileSize=1MB
#指定日志文件的数量
log4j.appender.rollingFile.maxBackupIndex=10
DailyRollingFileAppender按照文件日期拆分,根据设定的日期规则生成日志文件。
#按照时间规则拆分
#指定appender以文件形式输出
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
#指定文件的名称和路径
log4j.appender.dailyFile.file=/logs/log4j.log
#指定日志文件的字符集
log4j.appender.dailyFile.encoding=UTF-8
#日志格式
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.conversionPattern= [%p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
#按照日期拆分规则
log4j.appender.dailyFile.datePattern='.'yyyy-MM-dd
2.2.2 JDBCAppender
jdbcAppender就是将日志数据通过sql语句写入数据库中,因此需要提前建好一张表,再通过insert语句插入数据。
#JDBCAppender
log4j.appender.dbLog=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.dbLog.layout=org.apache.log4j.PatternLayout
log4j.appender.dbLog.Driver=com.mysql.jdbc.Driver
log4j.appender.dbLog.URL=jdbc:mysql://localhost:3306/test
log4j.appender.dbLog.User=root
log4j.appender.dbLog.Password=123456
log4j.appender.dbLog.Sql=INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('logDemo','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
(三)SLF4J
简单日志门面(Simple Logging Facade For Java)主要是为了给Java日志提供一套标准的API框架,具体的实现交由其他日志框架实现,比如Log4j。一般的SpringBoot项目会将slf4j作为门面,配合Log4j使用。接下来介绍slf4j绑定log4j的使用方式。
需要注意一点:slf4j同时只能有一个绑定日志框架,因此依赖SpringBoot的一些启动依赖可能会和下面的依赖冲突。学习时只需要下面几个依赖即可。
3.1 引入依赖:
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!--slf4j绑定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>
3.2 配置文件
将之前的log4j.properties配置文件放到resource目录下。
3.3 写代码
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogTest {
private static final Logger LOGGER= LoggerFactory.getLogger(LogTest.class);
@Test
public void test(){
//日志级别
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
//使用占位符输出
String name="java鱼仔";
String age="24";
LOGGER.info("name:{},age:{}",name,age);
}
}
3.3 slf4j桥接器
如果一个项目的日志框架原来用的是log4j,现在要改成logback,对slf4j来说只需要把log4j的依赖去除,然后加上logback的依赖即可,但是这时候会出现一个问题,以前用log4j写的代码就会报错。
slf4j提供了一种叫做桥接器的工具,只需要引入对应的依赖,就可以兼容旧的日志框架,并且以前代码中的Logger执行时自动更换成新的日志框架:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
注意,桥接器依赖不能和正式依赖一起出现!
(四)Log4j2
Log4j的升级版本,主要有下面的提升:
1、异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
2、性能提升,logj2相较于log4j和logback都具有很明显的性能提升。
3、自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用。
4、无垃圾机制,log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的GC。
log4j2本身既是日志门面,也是日志框架,不过现在Slf4j+Log4j2依然是热门的选型,下面就通过这种日志选型进行介绍。
4.1 引入依赖
<!--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>
<!--log4j2日志实现-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.10.0</version>
</dependency>
4.2 配置
log4j2在启动时会去resource路径下选择log4j2.xml的配置文件,这里列出一个xml模板,可以直接拿过去用
<?xml version="1.0" encoding="UTF-8"?>
<!--status="WARN" 日志框架本身的输出日志级别
monitorInterval="5" 自动加载配置文件的间隔时间不低于5秒
-->
<Configuration status="WARN" monitorInterval="5">
<!--全局参数,使用时可以通过${name}的方式使用-->
<properties>
<property name="pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %l %c{36} - %m%n</property>
<property name="logDir">/logs</property>
</properties>
<!--Logger定义,使用哪些Logger-->
<Loggers>
<Root level="INFO">
<AppenderRef ref="console"/>
<AppenderRef ref="rolling_file"/>
</Root>
</Loggers>
<Appenders>
<!-- 定义输出到控制台 -->
<Console name="console" target="SYSTEM_OUT" follow="true">
<!--控制台只输出level及以上级别的信息-->
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout>
<Pattern>${pattern}</Pattern>
</PatternLayout>
</Console>
<!-- 按照一定规则拆分的日志文件的appender -->
<RollingFile name="rolling_file"
fileName="${logDir}/rolling-file.log"
filePattern="${logDir}/rolling-file_%d{yyyy-MM-dd}.log">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout>
<Pattern>${pattern}</Pattern>
</PatternLayout>
<Policies>
<!--按照时间节点进行拆分-->
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<!-- 日志保留策略,配置只保留七天 -->
<DefaultRolloverStrategy>
<Delete basePath="${logDir}/" maxDepth="1">
<IfFileName glob="rolling-file_*.log" />
<IfLastModified age="7d" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
</Configuration>
4.3 代码的使用
在使用时其实就是用slf4j这个日志门面,只不过底层日志框架变成了log4j2
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogTest {
private static final Logger LOGGER= LoggerFactory.getLogger(LogTest.class);
@Test
public void test(){
//日志级别
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
//使用占位符输出
String name="java鱼仔";
String age="24";
LOGGER.info("name:{},age:{}",name,age);
}
}
(五)log4j2异步日志
之前将的所有日志框架都是通过同步的方式来打日志,log4j2提供了异步日志的功能,配置异步日志后,logger.info等代码生成的日志事件会被放到一个异步线程中去处理,从而不会影响系统的运行速度。下面介绍log4j2异步日志的实现方式:
5.1 引入依赖
<!--异步日志-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
5.2 局部异步处理
局部异步处理有两种方式,首先第一种是在Appender中添加异步处理:
主要有两步,第一步是在Appender中引入Async标签,第二步是在Loggers中引入异步标签
<Appenders>
<!-- 定义输出到控制台 -->
<Console name="console" target="SYSTEM_OUT" follow="true">
<!-- 内部的配置请看上面 -->
</Console>
<!-- 按照一定规则拆分的日志文件的appender -->
<RollingFile name="rolling_file"
fileName="${logDir}/rolling-file.log"
filePattern="${logDir}/rolling-file_%d{yyyy-MM-dd}.log">
<!-- 内部的配置请看上面 -->
</RollingFile>
<!--把rolling_file改为异步实现-->
<Async name="Async">
<AppenderRef ref="rolling_file"/>
</Async>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="console"/>
<!--引入异步的appender-->
<AppenderRef ref="Async"/>
</Root>
</Loggers>
第二种方式是在Loggers中添加异步日志定义:
<Loggers>
<!--
name表示自定义的异步Logger名称
level表示最低级别
includeLocation="false" 关闭日志记录的行号信息
additivity="false" 不继承rootlogger对象
-->
<AsyncLogger name="com.javayz" level="trace" includeLocation="false" additivity="false">
<AppenderRef ref="rolling_file"/>
</AsyncLogger>
<Root level="INFO">
<AppenderRef ref="console"/>
<AppenderRef ref="rolling_file"/>
</Root>
</Loggers>
5.3 全局异步
在resource文件下新建一个配置,命名为log4j2.component.properties,在这个文件中加一句:
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
(六)在SpringBoot中使用Logger
使用SpringBoot的日志需要引入一个启动依赖,这个依赖是包含在web启动依赖中的
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
SpringBoot中默认使用slf4j作为日志门面,使用logback作为日志的实现。
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringlogApplicationTests {
public static final Logger LOGGER= LoggerFactory.getLogger(SpringlogApplicationTests.class);
@Test
void contextLoads() {
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info"); //默认到info级别
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
通过application的简单配置即可使用:
#指定日志级别
logging.level.com.javayz=trace
#指定控制台输出的格式
logging.pattern.console=[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c [%thread] %msg %n
#指定日志文件存放路径,默认生成spring.log
logging.file.path=/logs/springboot
#指定文件中输出的格式
logging.pattern.file=[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c [%thread] %msg %n
如果觉得这些配置太简单,可以在resource目录下新建logback.xml(对应logback)
但是现在由于log4j2的性能要高于logback,一般项目中还是会选择log4j2,这时候就需要把默认的logback改成log4j2。修改方式:移除spring-boot-starter-logging依赖,引入log4j2依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--排除logging-->
<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>
然后在resource目录下新建log4j2.xml,按log4j2进行配置即可
(七)总结
日志的配置和使用很简单,但是确是找问题的好帮手,我是鱼仔,我们下期再见。