随着软件系统的发展系统业务越来越多、逻辑越来越复杂、代码量越来越多,伴随着容易出现的bug也会越来越多,不论是开发测试阶段还是生产阶段都需要将这些错误及时的捕捉记录下来,方便解决这些问题,否则针对出现的异常无从下手;此时就需要一些日志框架来完成这些事情并且最好能在出现问题时自动捕捉而不需要代码额外的处理,比如记录哪些类型的错误、日志格式、日志分类、日志扩展等等;一般Java有以下日志框架可选择,日志实现:JUL、logback、log4j、log4j2,日志门面:JCL、slf4j。
日志框架:用于记录日志的具体实现组件。
日志门面:因为每种日志框架记录日志的API各不相同,在使用某种日志框架时就需要使用它特定的API记录日志,如果项目后期改用其他框架那么就会导致改动很多记录日志的代码,所以通过日志门面进行统一API接口各种框架实现接口,那么即使更换框架日志记录也是一样的API,不需要做修改。
日志实现
一、JUL
JUL全称Java util Logging是java原生的日志框架,使用时不需要另外引用第三方类库,使用方便,学习简单(位于java.util.logging包下面)。
1、简单使用
两步:
java
//获取Logger对象,com.test.nginx.nginxtest.TestController需要输出日志的类全限定路径(当前类全限定路径)
Logger log = Logger.getLogger("com.test.nginx.nginxtest.TestController");
//输出日志
log.info("info");
代码:
java
package com.test.nginx.nginxtest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.logging.*;
@RequestMapping("/nginx")
@RestController
public class TestController {
//获取Logger对象,Logger对象存在父子级关系,对父级Logger对象的设置,子对象同样生效,通过路径组织Logger对象的父子级关系,比如 com.test.nginx 是 com.test 的子级
//即对Logger.getLogger("com.test") 对象的设置,对Logger.getLogger("com.test.nginx")对象同样生效,最顶级父对象是LogManager$RootLogger
private final Logger log = Logger.getLogger("com.test.nginx.nginxtest.TestController");
@GetMapping("/test/{value}")
public String test(@PathVariable("value") String value){
//设置日志输出级别,默认是info级别,即info和高于info级别的日志才会输出
//关闭默认级别
log.setUseParentHandlers(false);
//ConsoleHandler控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();
SimpleFormatter simpleFormatter = new SimpleFormatter();
consoleHandler.setFormatter(simpleFormatter);
log.addHandler(consoleHandler);
//设置日志级别为ALL,表示所有级别的日志都进行输出
log.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
try {
//FileHandler文件输出,E:\nginx-test\logs\jul.log文件需要已存在
FileHandler fileHandler = new FileHandler("E:\\nginx-test\\logs\\jul.log");
fileHandler.setFormatter(simpleFormatter);
log.addHandler(fileHandler);
} catch (IOException e) {
e.printStackTrace();
}
String a = "a对象";
Integer b = 2;
//输出severe级别的日志,相当于error日志
log.severe("severe");
//统一输出日志方法,日志级别通过参数控制,可以通过占位符替换日志中的参数
log.log(Level.WARNING,"日志输出:{0},大小{1}",new Object[]{a,b});
log.info("info");
log.config("config");
log.fine("fine");
log.finer("finer");
log.finest("finest");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(log.getParent());
return "你好8081,"+value;
}
}
2、配置文件方式使用
既然JUL是Java原生的日志框架,最顶级父类是 LogManager$RootLogger 意味着jdk内部应当集成了这个最顶级父类的默认配置文件,从而实现Logger对象的默认配置,这个配置文件在 JAVA_HOME 目录下(比如我的 JAVA_HOME 目录是 C:\Program Files\Java\jdk1.8.0_191 ,那么该配置文件所在位置是 C:\Program Files\Java\jdk1.8.0_191\jre\lib\logging.properties )
配置文件主要内容如下:
yaml
############################################################
# Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################
############################################################
# Global properties
############################################################
# "handlers" specifies a comma separated list of log Handler
# classes. These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler #指定处理器为ConsoleHandler,可以写多个如(也就是该配置决定在使用JUL记录日志时使用的Handler即开启哪个Handler,java.util.logging.ConsoleHandler表示在控制台输出日志,java.util.logging.FileHandler表示在文件中输出日志,需要控制台和文件中都输出日志则两个处理器都要写):
#handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers. For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO #指定日志级别
#如果没有指定具体的Logger名称表示对顶级父类 LogManager$RootLogger 的设置,比如上面的 handlers 和 .level 都是对顶级父类的配置,如果写了名称比如 com.handlers= java.util.logging.ConsoleHandler com.level= INFO 则表示该配置是对com这个Logger对象的配置(当然它的子类也生效),注意需要对 com 这个 Logger 对象关闭默认配置 com.useParentHandlers = false
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
# default file output is in user's home directory.
#java.util.logging.FileHandler 表示对 FileHandler(文件日志处理器) 处理器的配置
java.util.logging.FileHandler.pattern = %h/java%u.log #指定日志文件保存位置及日志文件名称 %h 当前目录,%u 表示数字几
java.util.logging.FileHandler.limit = 50000 #指定每个日志文件记录50000条日志
java.util.logging.FileHandler.count = 1 #指定日志文件个数比如9表示9个日志文件,那么上面的 %u 分别为0-8
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter #指定文件日志输出日志格式对象
java.util.logging.FileHandler.append = true #通过追加的形式将日志记录到文件日志中,否则之前的日志会被下一次的日志覆盖
# Limit the message that are printed on the console to INFO and above.
#java.util.logging.ConsoleHandler 表示对 ConsoleHandler(控制台日志处理器) 处理器的配置
java.util.logging.ConsoleHandler.level = INFO #指定控制台输出日志级别
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter #指定控制台输出日志格式对象
java.util.logging.ConsoleHandler.encoding = UTF-8 #指定控制台输出日志编码
#java.util.logging.SimpleFormatter 表示对 SimpleFormatter (简单日志格式对象)的设置
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n #指定 SimpleFormatter 日志格式
# Example to customize the SimpleFormatter output format
# to print one-line log message like this:
# <level>: <log message> [<date/time>]
#
# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE
在项目的resources目录下创建 logging.properties 文件并定义好配置
编码:
java
public static void main(String[] args) {
//读取配置文件获取输入流
InputStream resourceAsStream = TestJULController.class.getClassLoader().getResourceAsStream("logging.properties");
//获取 LogManager 对象
LogManager logManager = LogManager.getLogManager();
try {
//设置 LogManager 对象读取 resourceAsStream
logManager.readConfiguration(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
final Logger log = Logger.getLogger("com.test.nginx.nginxtest.TestJULController");
String a = "a对象";
Integer b = 2;
log.severe("severe");
log.log(Level.WARNING,"日志输出:{0},大小{1}",new Object[]{a,b});
log.info("info");
log.config("config");
log.fine("fine");
log.finer("finer");
log.finest("finest");
}
这是编码方式使用配置文件,一般spring boot项目用文章最后方式使用。
二、Log4j
Log4j是Apache下的一款开源的日志框架,通过在项目中使用 Log4J,可以控制日志信息输出到控制台、文件、数据库中;可以设置日志输出格式、日志输出级别。官网 (位于org.apache.log4j包下面)
导入包
xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
1、简单使用
java
package com.test.nginx.nginxtest;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/log4j")
@RestController
public class TestLog4jController {
@GetMapping("/test/{value}")
public String test(@PathVariable("value") String value){
return "你好8081,"+value;
}
public static void main(String[] args) {
//不使用配置文件需要初始化系统配置
BasicConfigurator.configure();
//获取Logger对象,可以使用需要打印日志的类的 Class 对象或类的全路径
Logger log = Logger.getLogger(TestLog4jController.class);
//打印日志,优先级从上到下依次降低,默认日志级别是 debug
log.log(Level.FATAL,"日志输出:fatal信息"); // 严重错误,一般会造成系统崩溃和终止运行,一般不打印这种级别的日志
log.error("日志输出:error信息"); //错误信息,但不会影响系统运行,也就是业务处理报错,但不会导致系统崩溃
log.warn("日志输出:warn信息"); //警告信息,可能会发生问题
log.info("日志输出:info信息"); //程序运行信息,数据库的连接、网络、IO操作等,程序正常运行且未有异常记录的日志
log.debug("日志输出:debug信息"); //调试信息,一般在开发阶段使用,记录程序的变量、参数等
log.trace("日志输出:trace信息"); //追踪信息,记录程序的所有流程信息,六种日志级别,一般不记录fatal和trace级别的日志,其余四种较常用,另外还有 OFF级别 用来关闭日志记录和 ALL级别 启用所有消息的日志记录
}
}
2、配置文件使用
在项目的resources目录下创建 log4j.properties 文件并定义好配置
yaml
log4j.rootLogger = trace,console,dailyFile,logDB #定义日志输出级别和输出类型,trace是日志级别,console和dailyFile、logDB是日志输出类型(输出到控制台)和文件中(DailyRollingFileAppender类型按照时间拆分日志)、数据库中,console和dailyFile、logDB是和下面定义的每种Appender名称对应的,这个名称可以自定义
#定义控制台类型日志信息,console是ConsoleAppender
log4j.appender.console = org.apache.log4j.ConsoleAppender #定义日志输出console类型的Appender
log4j.appender.console.layout = org.apache.log4j.PatternLayout #定义日志输出console类型的layout(格式)为PatternLayou(可自定义格式)
log4j.appender.console.layout.conversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %l - %m%n #自定义日志输出格式
#自定义Logger,同样的Log4j中rootLogger是最顶级的父类,只要配置rootLogger之后,那么所有的类输出日志时都会按照rootLogger定义的日志级别和日志类型输出,如果想要不同包下面的类分开设置日志级别和日志类型,就可以采用自定义Logger实现,比如下面就是对于com.test.nginx.nginxtest包下面的输出info及以上级别的日志并且输出到file中,对于org.apache下面的类输出error及以上级别的日志(对于日志级别子类会覆盖rootLogger设置的日志级别,对于输出类型则是并集,比如这里rootLogger定义了console,dailyFile,logDB三种类型,那么org.apache和com.test.nginx.nginxtest包下面的类都有这三种类型日志,并且com.test.nginx.nginxtest还多一个file类型的日志)
log4j.logger.com.test.nginx.nginxtest = info,file
log4j.logger.org.apache = error
#定义文件类型日志信息,file是FileAppender,将日志都输出在一个文件中
log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.conversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %l - %m%n
log4j.appender.file.file = logs/log4j.log #文件日志位置,不是 / 开头表示相对位置
log4j.appender.file.encoding = UTF-8 #设置日志编码
#定义文件类型日志信息,rollingFile是RollingFileAppender,可以根据日志文件大小将日志拆分为多个文件
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.conversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %l - %m%n
log4j.appender.rollingFile.file = logs/log4j.log
log4j.appender.rollingFile.encoding = UTF-8
log4j.appender.rollingFile.maxFileSize = 4KB #设置每个日志文件大小
log4j.appender.rollingFile.maxBackupIndex = 7 #设置日志文件总个数
#定义文件类型日志信息,dailyFile是DailyRollingFileAppender,将日志根据时间输出在多个文件中,具体文件名称中时间部分由log4j.appender.dailyFile.datePattern参数定义
log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.conversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %l - %m%n
log4j.appender.dailyFile.file = logs/log4j.log
log4j.appender.dailyFile.encoding = UTF-8
log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss #定义日志文件名称中日期格式,当前是精确到时分秒,那么不同日期不同时分秒的日志输出在多个文件中
#定义输出到数据库类型日志信息,logDB是JDBCAppender,将日志输出到定义的数据库中,插入内容由log4j.appender.logDB.Sql定义的sql决定
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.cj.jdbc.Driver #定义数据库驱动
log4j.appender.logDB.URL=jdbc:mysql://127.0.0.1:3306/test?setUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true #数据库url
log4j.appender.logDB.User=root #数据库账号
log4j.appender.logDB.Password=root #数据库密码
log4j.appender.logDB.Sql=INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m') #插入日志sql
定义为sql类型日志,需要在对应数据库中创建log表:
sql
CREATE TABLE `log` (
`log_id` int(11) NOT NULL AUTO_INCREMENT,
`project_name` varchar(255) DEFAULT NULL COMMENT '目项名',
`create_date` varchar(255) DEFAULT NULL COMMENT '创建时间',
`level` 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`)
);
定义为sql类型日志,需要在项目中导入数据库驱动jar包:
xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
编码:
java
package com.test.nginx.nginxtest;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/log4j")
@RestController
public class TestLog4jController {
@GetMapping("/test/{value}")
public String test(@PathVariable("value") String value){
return "你好8081,"+value;
}
public static void main(String[] args) {
//BasicConfigurator.configure(); 不使用配置文件需要初始化系统配置,如果使用配置文件则不需要
LogLog.setInternalDebugging(true); //开启Log4j内部日志输出(也就是会输出Log4j本身初始化运行等日志)
Logger log = Logger.getLogger("com.test.nginx.nginxtest.TestLog4jController");
log.log(Level.FATAL,"日志输出:fatal信息");
log.error("日志输出:error信息");
log.warn("日志输出:warn信息");
log.info("日志输出:info信息");
log.debug("日志输出:debug信息");
log.trace("日志输出:trace信息");
}
}
常见的Appender:
ConsoleAppender 将日志输出到控制台
FileAppender 将日志输出到文件中
DailyRollingFileAppender 将日志输出到一个日志文件,并且每天输出到一个新的文件
RollingFileAppender 将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件
JDBCAppender 把日志信息保存到数据库中
三种常见的Layout:
HTMLLayout 格式化日志输出为HTML表格形式 org.apache.log4j.HTMLLayout
SimpleLayout 简单的日志输出格式化,打印的日志格式为(info - message)org.apache.log4j.SimpleLayout
PatternLayout 最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式(默认格式比SimpleLayout更简单)org.apache.log4j.PatternLayout
XMLLayout 格式化日志输出为xml格式,不常用 org.apache.log4j.xml.XMLLayout
自定义格式 conversionPattern 取值:
%m 输出代码中指定的日志信息
%p 输出优先级,及 DEBUG、INFO 等
%n 换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n")
%r 输出自应用启动到输出该 log 信息耗费的毫秒数
%c 输出打印语句所属的类的全名
%t 输出产生该日志的线程全名
%d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%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字符,就从左边交远销出的字符截掉
三、logback
Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。官网
Logback主要有三个模块:
1、logback-core:其它两个模块的基础模块
2、logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API
3、logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能
导入包
xml
<!-- 这个包里面已经包含了 slf4j-api 所以使用方式按照 slf4j 日志门面的方式使用 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
1、简单使用
代码:
java
package com.test.nexus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestSlf4j {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(TestSlf4j.class);
//slf4j没有 fatal 级别的日志
logger.error("日志输出:error信息");
logger.warn("日志输出:warn信息");
logger.info("日志输出:info信息");
logger.debug("日志输出:debug信息");
logger.trace("日志输出:trace信息");
try {
throw new NullPointerException("空指针!");
}catch (Exception e){
logger.error("发生异常:",e);
}
}
}
2、配置文件使用
logback会依次读取以下类型配置文件:
1、logback.groovy
2、logback-test.xml
3、logback.xml 如果均不存在会采用默认配置
logback组件之间的关系
- Logger:日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。
- Appender:用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等。
- Layout:负责把事件转换成字符串,格式化的日志信息的输出。在logback中Layout对象被封装在encoder中(也就是说需要配置Layout时就通过配置encoder实现)。
在 resources 下创建 logback.xml 配置文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- property:自定义属性 后续可以通过${name}进行引用-->
<!-- property定义日志格式属性 -->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"/>
<!-- 日志输出格式含义:
%-5level 日志级别,-5表示占用5个字符并左对齐(-的含义)即日志级别从左显示5个字符宽度;如果没有5个字符采用空格补齐,也就是说比如 日志级别 ERROR 本身占用5个字符,则不需要占位符;INFO级别本身占用4个字符,则会用一个空格补齐5个字符
%d{yyyy-MM-dd HH:mm:ss.SSS} 日期格式
%c 类的完整名称
%M 方法名称
%L 打印日志的代码所在行号
%thread 线程名称
%m或%msg 打印的日志信息
%n 换行
-->
<!-- property定义日志文件存放目录属性 -->
<property name="log_dir" value="logs"></property>
<!-- Appender: 设置日志信息的去向,常用的有以下几个
ch.qos.logback.core.ConsoleAppender (控制台)
ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新文件)
ch.qos.logback.core.FileAppender (文件),默认追加,不会覆盖之前的日志
-->
<!-- 控制台输出 Appender 定义 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.err</target>
<!--日志格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 通过${name}的格式引用上面 property 定义的 pattern 名称的日志格式 -->
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!--日志文件输出 Appender 定义,文本格式 -->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!--日志格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!--日志输出路径-->
<file>${log_dir}/logback.log</file>
</appender>
<!-- 日志文件输出 Appender 定义,html格式 -->
<appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
<!--日志格式配置-->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%level%d{yyyy-MM-dd HH:mm:ss}%c%M%L%thread%m</pattern>
</layout>
</encoder>
<!--日志输出路径-->
<file>${log_dir}/logback.html</file>
</appender>
<!-- 日志文件输出 Appender 定义,文本格式,按照规则拆分日志文件 -->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!--日志输出路径,这个配置可以去掉,因为下面已经指定了按照大小和日期拆分文件和保存了,所以没必要再保存一份全在一起的日志文件 -->
<file>${log_dir}/roll_logback.log</file>
<!--指定日志文件拆分-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定日志文件名称,来确定分割文件方式,${log_dir}文件目录、rolling.%d{yyyy-MM-dd}-%i.log.gz表示文件名称(%i表示按照0开始步长1递增,加上.gz表示将文件自动压缩保存为压缩格式) -->
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
<!--每个日志文件拆分大小,上面指定.gz压缩格式后,是达到拆分大小之后才会将文件压缩,如果当前写入日志的文件大小并未达到拆分大小则不会保存为压缩格式-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
<!--filter配置-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--设置拦截日志级别,只拦截info这个级别的日志-->
<level>info</level>
<!-- 如果是info级别的日志则让通行即可以让当前appender处理 -->
<onMatch>ACCEPT</onMatch>
<!-- 如果不是info级别的日志则不让通行即当前appender不处理 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--异步日志,默认同步日志即采用和业务线程同一个线程记录日志,异步日志采用单独的线程记录日志避免记录日志影响业务响应,这里只对 rollFile 这个 appender 开启异步日志 -->
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="rollFile"/>
</appender>
<!-- <root> 也是<logger>元素,但是它是根logger。默认debug 级别
level:用来设置输出日志级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,<root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。
-->
<!-- 设置日志级别为 ALL -->
<root level="ALL">
<!-- 开启哪些 appender 即开启哪些日志输出类型,名称和上面定义的 appender 名称对应 -->
<appender-ref ref="console"/>
<appender-ref ref="file"/>
<appender-ref ref="htmlFile"/>
<appender-ref ref="rollFile"/>
</root>
<!-- <logger> 用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>即自定义Logger
<loger>仅有一个name属性,一个可选的level和一个可选的addtivity属性
name: 用来指定受此logger约束的某一个包或者具体的某一个类。
level: 用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,如果未设置此属性,那么当前logger将会继承上级的级别。
additivity: 是否继承上级logger配置,默认是true。
<logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger
-->
<!--自定义logger additivity表示是否从 rootLogger 继承配置,也就是 com.test.nexus 包下面的类记录日志时就采用这个自定义的Logger记录,日志级别为debug、只在 console 控制台输出日志,其他包下面的日志记录还是 rootLogger -->
<logger name="com.test.nexus" level="debug" additivity="false">
<appender-ref ref="console"/>
</logger>
</configuration>
四、Log4j2
Apache Log4j 2是对Log4j的升级版,参考了logback的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:
1、异常处理:在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
2、性能提升:log4j2相较于log4j 和logback都具有很明显的性能提升。
3、自动重载配置:参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用。
4、无垃圾机制:log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc。
官网
目前市面上最主流的日志门面就是SLF4J,虽然Log4j2也是日志门面,但它的日志实现功能非常强大,性能优越。所以一般还是将Log4j2看作是日志的实现,采用 Slf4j + Log4j2 是主流日志记录搭配。
导入包
xml
<!-- Log4j2 门面API-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<!-- Log4j2 日志实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
1、简单使用
目前导入的是 log4j-api 日志门面,所以以下代码使用的 Log4j2 的日志门面进行的日志输出
java
package com.test.nexus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TestLog4j2 {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(TestLog4j2.class);
logger.fatal("日志输出:fatal信息");
logger.error("日志输出:error信息");
logger.warn("日志输出:warn信息");
logger.info("日志输出:info信息");
logger.debug("日志输出:debug信息");
logger.trace("日志输出:trace信息");
}
}
使用 Slf4j 日志门面输出日志
导入 Slf4j 相关包
xml
<!--为slf4j绑定日志实现 log4j2 的适配器,因为其中已经包含了 slf4j-api 所以不需要额外导入 slf4j-api 的包-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.10.0</version>
</dependency>
代码:
java
package com.test.nexus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestLog4j2Slf4j {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(TestLog4j2Slf4j.class);
//slf4j没有 fatal 级别的日志
logger.error("日志输出:error信息");
logger.warn("日志输出:warn信息");
logger.info("日志输出:info信息");
logger.debug("日志输出:debug信息");
logger.trace("日志输出:trace信息");
}
}
2、配置文件使用
在 resources 目录下创建 log4j2.xml 文件并配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- status="warn"表示设置log4j2内部日志(log4j2框架本身的日志)输出的级别,monitorInterval="5"表示5秒钟加载一次log4j2的配置文件即实现热更新log4j2的配置文件-->
<Configuration status="warn" monitorInterval="5">
<properties>
<!-- properties中定义引用属性,比如这里定义一个名称为 LOG_HOME 的属性,值是 logs -->
<property name="LOG_HOME">logs</property>
</properties>
<Appenders>
<!-- 定义控制台输出 Appender ,SYSTEM_OUT表示输出的日志是黑色,SYSTEM_ERR表示输出的日志是红色-->
<Console name="Console" target="SYSTEM_OUT">
<!-- 定义日志输出格式 格式代表含义和 logback 的一致 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
</Console>
<!-- 定义文件输出 Appender ,fileName设置保存日志文件的位置名称信息,${LOG_HOME}引用上面定义的 property -->
<File name="file" fileName="${LOG_HOME}/myfile.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
</File>
<!-- 配置 AsyncAppender 方式的异步日志,性能和logback、log4j差不多不推荐使用 -->
<Async name="Async">
<!-- 对名称为 file 的 Appender 开启 AsyncAppender -->
<AppenderRef ref="file"/>
</Async>
<!-- 定义文件输出 Appender ,使用随机读写流将日志输出到日志文件中,提升性能 -->
<RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
</RandomAccessFile>
<!-- 定义文件输出 Appender ,按照规则拆分日志文件 fileName 定义的文件位置及名称会直接输出日志,filePattern 定义的位置和文件名称只有输出的日志达到下面定义的 SizeBasedTriggeringPolicy 大小之后才会生效,才会在定义的位置输出日志文件 -->
<RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log" filePattern="logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
<!-- 日志级别过滤器,定义的级别及以上的级别当前Appender才会输出,以下的级别当前Appender不会输出,这里定义为 debug 级别 -->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
<!-- 日志拆分规则 -->
<Policies>
<!-- 每次系统启动时都生成一个新的日志文件 -->
<OnStartupTriggeringPolicy />
<!-- 每个日志文件按照1MB大小拆分 -->
<SizeBasedTriggeringPolicy size="1MB" />
<!-- 按照时间节点拆分,规则是上面 filePattern 定义的-->
<TimeBasedTriggeringPolicy />
</Policies>
<!-- 同一个目录下允许存在最大的日志文件个数,超过则按照时间越远进行覆盖 -->
<DefaultRolloverStrategy max="30" />
</RollingFile>
</Appenders>
<!-- 设置 Logger 信息 -->
<Loggers>
<!-- 自定义Logger,com.test.nexus 包下面的类都采用当前定义的 AsyncLogger 做日志输出,而不是 rootLogger 做输出,日志级别 trace,includeLocation="false" 表示关闭日志的行号输出(不输出打印日志的代码行号,开启会影响性能甚至比同步性能更差),additivity="false" 表示不继承rootLogger的配置 -->
<AsyncLogger name="com.test.nexus" level="trace" includeLocation="false" additivity="false">
<AppenderRef ref="Console"/>
</AsyncLogger>
<!-- 设置 rootLogger 信息,日志级别为 trace -->
<Root level="trace">
<!-- 设置 Appender 信息,启用的 Appender 名称是 Console -->
<AppenderRef ref="Console" />
<AppenderRef ref="file" />
<AppenderRef ref="rollingFile"/>
<!-- 启用的 Appender 名称是 Async,Async是定义的 AsyncAppender,因此使用 AsyncAppender 需要在 Appender 中定义还需要开启这个 Appender -->
<AppenderRef ref="Async"/>
</Root>
</Loggers>
</Configuration>
如果配置异步日志,需要导入异步日志依赖
xml
<!--异步日志依赖-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
Log4j2提供了两种实现异步日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应Appender组件和Logger组件。
其中 AsyncAppender 方式性能和logback、log4j等差别不大,不建议使用。
AsyncLogger推荐使用 ,有两种配置方式:
全局异步配置:所有的日志都异步的记录,在配置文件上不用做任何改动,只需要在 resources 目录下添加一个 log4j2.component.properties 文件并加入以下配置即可.
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
混合异步配置:可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活(先关闭全局的 AsyncLogger 否则就是全局异步而非混合异步),在 Loggers 标签中配置 AsyncLogger 信息即可。
xml
<Loggers>
<!-- 自定义Logger,com.test.nexus 包下面的类都采用当前定义的 AsyncLogger 做日志输出,而不是 rootLogger 做输出,日志级别 trace,includeLocation="false" 表示关闭日志的行号输出(不输出打印日志的代码行号,开启会影响性能甚至比同步性能更差),additivity="false" 表示不继承rootLogger的配置 -->
<AsyncLogger name="com.test.nexus" level="trace" includeLocation="false" additivity="false">
<AppenderRef ref="Console"/>
</AsyncLogger>
</Loggers>
使用异步日志需要注意的问题:
- 如果使用异步日志,AsyncAppender、AsyncLogger(包括其中的全局异步和混合异步),不要同时出现。性能会和AsyncAppender一致,降至最低。
- 设置includeLocation=false ,打印位置信息(输出日志的代码行号)会急剧降低异步日志的性能,比同步日志还要慢。
无垃圾模式
垃圾收集暂停是延迟峰值的常见原因,并且对于许多系统而言,花费大量精力来控制这些暂停,许多日志库(包括以前版本的Log4j)在稳态日志记录期间分配临时对象,如日志事件对象,字符串,字符数组,字节数组等;这会对垃圾收集器造成压力并增加GC暂停发生的频率。所以从版本2.6开始,默认情况下Log4j以"无垃圾"模式运行,其中重用对象和缓冲区,并且尽可能不分配临时对象。还有一个"低垃圾"模式,它不是完全无垃圾,但不使用ThreadLocal字段。Log4j 2.6中的无垃圾日志记录部分通过重用ThreadLocal字段中的对象来实现,部分通过在将文本转换为字节时重用缓冲区来实现。
Log4j 2.5:内存分配速率809 MB /秒,141个无效集合。
Log4j 2.6:没有分配临时对象,0(零)垃圾回收。
有两个单独的系统属性可用于手动控制Log4j用于避免创建临时对象的机制:
log4j2.enableThreadlocals - 如果"true"(非Web应用程序的默认值)对象存储在ThreadLocal字段中并重新使用,否则将为每个日志事件创建新对象。
log4j2.enableDirectEncoders - 如果将"true"(默认)日志事件转换为文本,则将此文本转换为字节而不创建临时对象。注意: 由于共享缓冲区上的同步,在此模式下多线程应用程序的同步日志记录性能可能更差。如果您的应用程序是多线程的并且日志记录性能很重要,请考虑使用异步记录器。
这些都不需要手动设置,只需要将 log4j2 的版本升级为 2.6 及以上即可。 也就是使用log4j2作为日志实现时使用2.6及以上的版本,并可以采用 AsyncLogger 的全局或混合异步提升性能。
日志门面
JCL和SLF4J
日志门面技术作用:
- 面向接口开发,不再依赖具体的实现类。减少代码的耦合
- 项目通过导入不同的日志实现类,可以灵活的切换日志框架
- 统一API,方便开发者学习和使用
- 统一配置便于项目日志的管理
一、JCL
全称Jakarta Commons Logging,是Apache提供的一个通用日志API,它是为 "所有的Java日志实现"提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常常弱(SimpleLog)。一般不会单独使用它,允许开发人员使用不同的具体日志实现工具: Log4j和jdk自带的日志(JUL)。
JCL 有两个基本的抽象类:Log(基本记录器)和LogFactory(负责创建Log实例)。
导入JCL jar包
xml
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
编写使用日志代码:
java
package com.test;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class TestSec {
public static void main(String[] args) {
Log log = LogFactory.getLog(TestSec.class);
log.fatal("日志输出:fatal信息");
log.error("日志输出:error信息");
log.warn("日志输出:warn信息");
log.info("日志输出:info信息");
log.debug("日志输出:debug信息");
log.trace("日志输出:trace信息");
}
}
导入Log4j的jar包后测试,报错如下,提示需要添加 appenders。
按照上面 Log4j 添加 log4j.properties 配置文件配置即可(按需要配置 ConsoleAppender 等 appenders 即可)。
再次测试(在 log4j.properties 配置文件中设置了日志级别是 trace 所以日志都有输出):
JCL实现日志框架适配的方式:
在JCL内部定义了一个数组,这个数组里面定义的四种就是目前JCL支持的日志实现框架,然后通过循环数组,找到目前项目中有哪些类,从而创建对应日志框架的Logger对象,可以看出来优先级分别是先判断Log4JLogger、Jdk14Logger、Jdk13LumberjackLogger、SimpleLog,只要找到一个实现就返回对应Logger对象,因此JCL本身虽然有个SimpleLog的实现,但是都会被优先级更高的jdk自带的JUL实现(Jdk14Logger、Jdk13LumberjackLogger这两种都是jdk的JUL),同样的项目导入 Log4j 的包之后就会优先实现Log4j。
java
private static final String[] classesToDiscover =
new String[]{"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"};
for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
result = this.createLogFromClass(classesToDiscover[i], logCategory,
true);
}
二、SLF4J
简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等;slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。官网
JCL只支持JUL和Log4j(性能和功能不如logback、log4j2),而SLF4J对于JUL、Log4j 、logback、log4j2都支持,是目前市面上最流行的日志门面。项目中,基本上都是使用SLF4J作为日志门面系统。SLF4J日志门面主要提供两大功能:1. 日志框架的绑定 2. 日志框架的桥接。
slf4j 使用日志实现框架:
- 添加slf4j-api的依赖
- 绑定具体的日志实现框架
- 绑定已经实现了slf4j的日志框架,直接添加对应依赖(slf4j-simple、logback、jul、slf4j-nop遵循slf4j实现)
- 绑定没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖(log4j、log4j2需要导入适配器)
- 使用slf4j的API在项目中进行统一的日志记录
- slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)
导入包
xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.27</version>
</dependency>
代码:
java
package com.test.nexus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestSlf4j {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(TestSlf4j.class);
//slf4j没有 fatal 级别的日志
logger.error("日志输出:error信息");
logger.warn("日志输出:warn信息");
logger.info("日志输出:info信息");
logger.debug("日志输出:debug信息");
logger.trace("日志输出:trace信息");
try {
throw new NullPointerException("空指针!");
}catch (Exception e){
logger.error("发生异常:",e);
}
}
}
此时只是有日志门面并没有日志实现框架,会报错。
导入某个具体的日志实现框架。
slf4j-simple slf4j自己实现的简单日志实现框架
xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.27</version>
</dependency>
logback
xml
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
如果有多个日志实现框架会出现提示,并默认使用第一个日志框架:
去除其他日志实现框架,只导入 logback 后:
jul
xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.27</version>
</dependency>
禁用slf4j日志输出
xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.27</version>
</dependency>
以上四种日志实现框架因为遵循了 slf4j 的 api 规范,所以导入 slf4j 的包以及这四种日志框架实现包之一后,就可以直接使用。
log4j、log4j2需要做适配。
log4j
xml
<!-- 导入适配器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.27</version>
</dependency>
再导入log4j的包以及配置 log4j.properties 文件,然后测试
如果测试提示需要配置 appenders ;但已经在 log4j.properties 文件中配置了,那么就将项目 Rebuild 下,再测试。
注意:很多包里面都包含了一些基础的包,因此某些基础的包可以不用导入;比如使用 slf4j 时需要导入基础包 slf4j-api 但具体的日志实现框架,比如 slf4j-log4j12(适配器)、slf4j-simple、logback-classic、slf4j-nop、slf4j-jdk14等都包含了 slf4j-api 包,因此导入这些日志实现框架后就可以直接使用 slf4j 了。
Spring Boot日志配置
spring boot项目导入的核心包 spring-boot-starter-web 其中已经包含了相关日志门面和日志实现的包,并且spring boot默认是使用SLF4J作为日志门面,logback作为日志实现来记录日志;因此spring boot项目是可以直接使用日志功能的。
总结:
- springboot 底层默认使用logback作为日志实现。
- 使用了SLF4J作为日志门面
- 将JUL也转换成slf4j
- 也可以使用log4j2作为日志门面,但是最终也是通过slf4j调用logback
使用 @Slf4j 注解导入额外两个包 lombok和log4j
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
编码:
java
package com.my.test.member.biz.controller;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j //@Slf4j注解需要导入两个包(lombok和log4j)才能使用
@RestController
@RequestMapping("testlog")
public class TestLogController {
final Logger logger = LoggerFactory.getLogger(TestLogController.class);//LoggerFactory不需要导入额外的包就可使用
@GetMapping("/info/{name}")
public String info(@PathVariable("name") String name){
logger.error("日志输出:error信息:"+name);
logger.warn("日志输出:warn信息:"+name);
logger.info("日志输出:info信息:"+name);
logger.debug("日志输出:debug信息:"+name);
logger.trace("日志输出:trace信息:"+name);
log.error("日志输出:error信息:"+name+"注解");
log.warn("日志输出:warn信息:"+name+"注解");
log.info("日志输出:info信息:"+name+"注解");
log.debug("日志输出:debug信息:"+name+"注解");
log.trace("日志输出:trace信息:"+name+"注解");
return "成功!";
}
}
以上都是直接使用默认配置,下面添加日志配置文件:
在resources目录下创建以下配置文件:
日志框架 | 配置文件 |
---|---|
Logback | logback-spring.xml 、logback.xml |
Log4j2 | log4j2-spring.xml 、 log4j2.xml |
JUL | logging.properties |
resources 目录下创建 logback-spring.xml 配置文件:
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- property:自定义属性 后续可以通过${name}进行引用-->
<!-- property定义日志格式属性 -->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"/>
<!-- property定义日志文件存放目录属性 -->
<property name="log_dir" value="logs"></property>
<!-- 控制台输出 Appender 定义 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out(日志黑色) 改为 System.err(日志红色) -->
<target>System.err</target>
<!--日志格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- springProfile作用是可以定义环境信息,配合 spring.profiles.active = dev/pro 实现切换不同环境的日志配置,由项目中的spring.profiles.active配置决定使用的日志配置,但这个要能被识别只能是-spring的配置文件,如logback-spring.xml而logback.xml则不能使用该功能 -->
<springProfile name="dev">
<!-- 通过${name}的格式引用上面 property 定义的 pattern 名称的日志格式 -->
<pattern>${pattern}</pattern>
</springProfile>
<springProfile name="pro">
<pattern>%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level %msg%n </pattern>
</springProfile>
</encoder>
</appender>
<!--日志文件输出 Appender 定义,文本格式 -->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!--日志格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!--日志输出路径-->
<file>${log_dir}/logback.log</file>
</appender>
<!-- 日志文件输出 Appender 定义,html格式 -->
<appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
<!--日志格式配置-->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%level%d{yyyy-MM-dd HH:mm:ss}%c%M%L%thread%m</pattern>
</layout>
</encoder>
<!--日志输出路径-->
<file>${log_dir}/logback.html</file>
</appender>
<!-- 日志文件输出 Appender 定义,文本格式,按照规则拆分日志文件 -->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!--日志输出路径,这个配置可以去掉,因为下面已经指定了按照大小和日期拆分文件和保存了,所以没必要再保存一份全在一起的日志文件 -->
<file>${log_dir}/roll_logback.log</file>
<!--指定日志文件拆分-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定日志文件名称,来确定分割文件方式,${log_dir}文件目录、rolling.%d{yyyy-MM-dd}-%i.log.gz表示文件名称(%i表示按照0开始步长1递增,加上.gz表示将文件自动压缩保存为压缩格式) -->
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
<!--每个日志文件拆分大小,上面指定.gz压缩格式后,是达到拆分大小之后才会将文件压缩,如果当前写入日志的文件大小并未达到拆分大小则不会保存为压缩格式-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
<!--filter配置-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--设置拦截日志级别,只拦截info这个级别的日志-->
<level>info</level>
<!-- 如果是info级别的日志则让通行即可以让当前appender处理 -->
<onMatch>ACCEPT</onMatch>
<!-- 如果不是info级别的日志则不让通行即当前appender不处理 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 设置日志级别为 info,日志级别不宜设置太低,否则会打印很多框架内部的日志,建议info及以上 -->
<root level="info">
<!-- 开启哪些 appender 即开启哪些日志输出类型,名称和上面定义的 appender 名称对应 -->
<appender-ref ref="console"/>
<appender-ref ref="file"/>
<appender-ref ref="htmlFile"/>
<appender-ref ref="rollFile"/>
</root>
</configuration>
添加配置文件后可以直接测试,因为 配置文件能被spring boot自动识别,且默认使用 logback 的日志实现。
切换日志实现,项目主流采用 slf4j+log4j2的搭配,那么需要排除 logback 的依赖,spring boot就会自动使用项目中目前存在的日志实现框架。
排除并添加依赖
xml
<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>
<!--如果需要异步日志则导入异步日志依赖-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
resources 目录下创建 log4j2-spring.xml 配置文件(实现error、warn、info级别日志分开目录及文件记录,AsyncLogger 混合异步日志示例):
xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- status="warn"表示设置log4j2内部日志(log4j2框架本身的日志)输出的级别,monitorInterval="5"表示5秒钟加载一次log4j2的配置文件即实现热更新log4j2的配置文件-->
<Configuration status="warn" monitorInterval="5">
<properties>
<!-- properties中定义引用属性,比如这里定义一个名称为 LOG_HOME 的属性,值是 logs -->
<property name="LOG_HOME">logs</property>
</properties>
<Appenders>
<!-- 定义控制台输出 Appender ,SYSTEM_OUT表示输出的日志是黑色,SYSTEM_ERR表示输出的日志是红色-->
<Console name="Console" target="SYSTEM_OUT">
<!-- 定义日志输出格式 格式代表含义和 logback 的一致 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/>
</Console>
<!-- 定义文件输出 Appender ,fileName设置保存日志文件的位置名称信息,${LOG_HOME}引用上面定义的 property -->
<File name="file" fileName="${LOG_HOME}/myfile.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n"/>
</File>
<!-- 定义文件输出 Appender ,使用随机读写流将日志输出到日志文件中,提升性能 -->
<RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n"/>
</RandomAccessFile>
<!-- 定义文件输出 Appender ,按照规则拆分日志文件 fileName 定义的文件位置及名称会直接输出日志,filePattern 定义的位置和文件名称只有输出的日志达到下面定义的 SizeBasedTriggeringPolicy 大小之后才会生效,才会在定义的位置输出日志文件 -->
<!-- 生成的日志会先存放在 fileName 定义的文件中,符合条件后才会将(比如这里filePattern定义为分钟命名,那么下一分钟的请求到来时,上一分钟的日志就会被转移) fileName 中已记录的日志转移到 filePattern 定义的文件中 -->
<RollingFile name="rollingErrorFile" fileName="${LOG_HOME}/error.log"
filePattern="logs/$${date:yyyy-MM-dd}/error/error-%d{yyyy-MM-dd-HH-mm}-%i.log">
<!--
onMatch DENY/ACCEPT 是否接受匹配到的日志(ACCEPT表示处理匹配的日志,DENY不处理匹配的日志)
onMismatch DENY/NEUTRAL 是否接受未匹配的日志(DENY表示不处理未匹配的日志并且拦截掉日志,NEUTRAL也是不处理未匹配的日志,但会将日志放行交给下一个ThresholdFilter处理)
-->
<!-- 日志级别过滤器,定义的级别及以上的级别当前Appender才会输出,以下的级别当前Appender不会输出,这里定义为 debug 级别 -->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n"/>
<!-- 日志拆分规则 -->
<Policies>
<!-- 每次系统启动时都生成一个新的日志文件 -->
<OnStartupTriggeringPolicy/>
<!-- 每个日志文件按照1MB大小拆分 -->
<SizeBasedTriggeringPolicy size="1MB"/>
<!-- 按照时间节点拆分,规则是上面 filePattern 定义的-->
<TimeBasedTriggeringPolicy/>
</Policies>
<!-- 同一个目录下允许存在最大的日志文件个数,超过则按照时间越远进行覆盖 -->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- 定义文件输出 Appender ,按照规则拆分日志文件 fileName 定义的文件位置及名称会直接输出日志,filePattern 定义的位置和文件名称只有输出的日志达到下面定义的 SizeBasedTriggeringPolicy 大小之后才会生效,才会在定义的位置输出日志文件 -->
<RollingFile name="rollingWarnFile" fileName="${LOG_HOME}/warn.log"
filePattern="logs/$${date:yyyy-MM-dd}/warn/warn-%d{yyyy-MM-dd-HH-mm}-%i.log">
<!-- 日志级别过滤器,定义的级别及以上的级别当前Appender才会输出,以下的级别当前Appender不会输出,这里定义为 debug 级别 -->
<Filters>
<!-- 按照这里的配置,首先 error 级别的日志匹配后不会被处理,未匹配到的日志(除error之外的)交给下一个 ThresholdFilter 处理 -->
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<!-- 经过前面的过滤到这个 ThresholdFilter 的日志不包含 error 级别了,而当前 ThresholdFilter 只处理 warn及以上的日志(error),所以这里相当于只对 warn 级别的日志做处理,那么匹配的进行处理,未匹配的不处理并拦截 -->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n"/>
<!-- 日志拆分规则 -->
<Policies>
<!-- 每次系统启动时都生成一个新的日志文件 -->
<OnStartupTriggeringPolicy/>
<!-- 每个日志文件按照1MB大小拆分 -->
<SizeBasedTriggeringPolicy size="1MB"/>
<!-- 按照时间节点拆分,规则是上面 filePattern 定义的-->
<TimeBasedTriggeringPolicy/>
</Policies>
<!-- 同一个目录下允许存在最大的日志文件个数,超过则按照时间越远进行覆盖 -->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- 定义文件输出 Appender ,按照规则拆分日志文件 fileName 定义的文件位置及名称会直接输出日志,filePattern 定义的位置和文件名称只有输出的日志达到下面定义的 SizeBasedTriggeringPolicy 大小之后才会生效,才会在定义的位置输出日志文件 -->
<RollingFile name="rollingInfoFile" fileName="${LOG_HOME}/info.log"
filePattern="logs/$${date:yyyy-MM-dd}/info/info-%d{yyyy-MM-dd-HH-mm}-%i.log">
<!-- 日志级别过滤器,定义的级别及以上的级别当前Appender才会输出,以下的级别当前Appender不会输出,这里定义为 debug 级别 -->
<Filters>
<!-- 这里也一样,第一个 ThresholdFilter 将warn和error级别的日志匹配后不处理,其他级别的日志放行,到第二个 ThresholdFilter 处理时只有 info及以下的日志,而 ThresholdFilter 只对info及以上的日志处理,所以相当于只对info做处理,匹配的处理,未匹配的不处理并拦截 -->
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n"/>
<!-- 日志拆分规则 -->
<Policies>
<!-- 每次系统启动时都生成一个新的日志文件 -->
<OnStartupTriggeringPolicy/>
<!-- 每个日志文件按照1MB大小拆分 -->
<SizeBasedTriggeringPolicy size="1MB"/>
<!-- 按照时间节点拆分,规则是上面 filePattern 定义的-->
<TimeBasedTriggeringPolicy/>
</Policies>
<!-- 同一个目录下允许存在最大的日志文件个数,超过则按照时间越远进行覆盖 -->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<!-- 设置 Logger 信息 -->
<Loggers>
<!-- 自定义Logger,com.my.test.member.biz.controller 包下面的类都采用当前定义的 AsyncLogger 做日志输出,而不是 rootLogger 做输出,日志级别 trace,includeLocation="false" 表示关闭日志的行号输出(不输出打印日志的代码行号,开启会影响性能甚至比同步性能更差),additivity="false" 表示不继承rootLogger的配置 -->
<!-- AsyncLogger 配置异步日志,需要导入异步日志的依赖 disruptor -->
<AsyncLogger name="com.my.test.member.biz.controller" level="trace" includeLocation="false" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="rollingErrorFile"/>
<AppenderRef ref="rollingWarnFile"/>
<AppenderRef ref="rollingInfoFile"/>
</AsyncLogger>
<!-- 设置 rootLogger 信息,日志级别为 info,不建议日志级别太低,否则会打印很多系统的日志 -->
<Root level="info">
<!-- 设置 Appender 信息,启用的 Appender 名称是 Console -->
<AppenderRef ref="Console"/>
<AppenderRef ref="file"/>
<AppenderRef ref="rollingErrorFile"/>
<AppenderRef ref="rollingWarnFile"/>
<AppenderRef ref="rollingInfoFile"/>
</Root>
</Loggers>
</Configuration>