随着软件系统的发展系统业务越来越多、逻辑越来越复杂、代码量越来越多,伴随着容易出现的bug也会越来越多,不论是开发测试阶段还是生产阶段都需要将这些错误及时的捕捉记录下来,方便解决这些问题,否则针对出现的异常无从下手;此时就需要一些日志框架来完成这些事情并且最好能在出现问题时自动捕捉而不需要代码额外的处理,比如记录哪些类型的错误、日志格式、日志分类、日志扩展等等;一般Java有以下日志框架可选择,日志实现:JUL、logback、log4j、log4j2,日志门面:JCL、slf4j。
日志框架:用于记录日志的具体实现组件。
日志门面:因为每种日志框架记录日志的API各不相同,在使用某种日志框架时就需要使用它特定的API记录日志,如果项目后期改用其他框架那么就会导致改动很多记录日志的代码,所以通过日志门面进行统一API接口各种框架实现接口,那么即使更换框架日志记录也是一样的API,不需要做修改。
JUL全称Java util Logging是java原生的日志框架,使用时不需要另外引用第三方类库,使用方便,学习简单(位于java.util.logging包下面)。
两步:
//获取Logger对象,com.test.nginx.nginxtest.TestController需要输出日志的类全限定路径(当前类全限定路径)
Logger log = Logger.getLogger("com.test.nginx.nginxtest.TestController");
//输出日志
log.info("info");
代码:
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;
}
}
既然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 )
配置文件主要内容如下:
############################################################
# 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:
# : []
#
# 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 文件并定义好配置
编码:
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是Apache下的一款开源的日志框架,通过在项目中使用 Log4J,可以控制日志信息输出到控制台、文件、数据库中;可以设置日志输出格式、日志输出级别。官网 (位于org.apache.log4j包下面)
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
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级别 启用所有消息的日志记录
}
}
在项目的resources目录下创建 log4j.properties 文件并定义好配置
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表:
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包:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.22version>
dependency>
编码:
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信息");
}
}
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是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。官网
Logback主要有三个模块:
1、logback-core:其它两个模块的基础模块
2、logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API
3、logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
代码:
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);
}
}
}
logback会依次读取以下类型配置文件:
1、logback.groovy
2、logback-test.xml
3、logback.xml 如果均不存在会采用默认配置
logback组件之间的关系
在 resources 下创建 logback.xml 配置文件
<configuration>
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"/>
<property name="log_dir" value="logs">property>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<target>System.errtarget>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}pattern>
encoder>
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.logfile>
appender>
<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%mpattern>
layout>
encoder>
<file>${log_dir}/logback.htmlfile>
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.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}-%i.log.gzfileNamePattern>
<maxFileSize>1MBmaxFileSize>
rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>infolevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="rollFile"/>
appender>
<root level="ALL">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
<appender-ref ref="htmlFile"/>
<appender-ref ref="rollFile"/>
root>
<logger name="com.test.nexus" level="debug" additivity="false">
<appender-ref ref="console"/>
logger>
configuration>
Apache Log4j 2是对Log4j的升级版,参考了logback的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:
1、异常处理:在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
2、性能提升:log4j2相较于log4j 和logback都具有很明显的性能提升。
3、自动重载配置:参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用。
4、无垃圾机制:log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc。
官网
目前市面上最主流的日志门面就是SLF4J,虽然Log4j2也是日志门面,但它的日志实现功能非常强大,性能优越。所以一般还是将Log4j2看作是日志的实现,采用 Slf4j + Log4j2 是主流日志记录搭配。
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>2.11.1version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.11.1version>
dependency>
目前导入的是 log4j-api 日志门面,所以以下代码使用的 Log4j2 的日志门面进行的日志输出
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 相关包
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>2.10.0version>
dependency>
代码:
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信息");
}
}
在 resources 目录下创建 log4j2.xml 文件并配置
<Configuration status="warn" monitorInterval="5">
<properties>
<property name="LOG_HOME">logsproperty>
properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
Console>
<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>
<Async name="Async">
<AppenderRef ref="file"/>
Async>
<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>
<RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log" filePattern="logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
<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 />
<SizeBasedTriggeringPolicy size="1MB" />
<TimeBasedTriggeringPolicy />
Policies>
<DefaultRolloverStrategy max="30" />
RollingFile>
Appenders>
<Loggers>
<AsyncLogger name="com.test.nexus" level="trace" includeLocation="false" additivity="false">
<AppenderRef ref="Console"/>
AsyncLogger>
<Root level="trace">
<AppenderRef ref="Console" />
<AppenderRef ref="file" />
<AppenderRef ref="rollingFile"/>
<AppenderRef ref="Async"/>
Root>
Loggers>
Configuration>
如果配置异步日志,需要导入异步日志依赖
<dependency>
<groupId>com.lmaxgroupId>
<artifactId>disruptorartifactId>
<version>3.3.4version>
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 信息即可。
<Loggers>
<AsyncLogger name="com.test.nexus" level="trace" includeLocation="false" additivity="false">
<AppenderRef ref="Console"/>
AsyncLogger>
Loggers>
使用异步日志需要注意的问题:
JCL和SLF4J
日志门面技术作用:
全称Jakarta Commons Logging,是Apache提供的一个通用日志API,它是为 "所有的Java日志实现"提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常常弱(SimpleLog)。一般不会单独使用它,允许开发人员使用不同的具体日志实现工具: Log4j和jdk自带的日志(JUL)。
JCL 有两个基本的抽象类:Log(基本记录器)和LogFactory(负责创建Log实例)。
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.2version>
dependency>
编写使用日志代码:
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。
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);
}
简单日志门面(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 使用日志实现框架:
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.27version>
dependency>
代码:
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自己实现的简单日志实现框架
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-simpleartifactId>
<version>1.7.27version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
如果有多个日志实现框架会出现提示,并默认使用第一个日志框架:
去除其他日志实现框架,只导入 logback 后:
jul
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-jdk14artifactId>
<version>1.7.27version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-nopartifactId>
<version>1.7.27version>
dependency>
以上四种日志实现框架因为遵循了 slf4j 的 api 规范,所以导入 slf4j 的包以及这四种日志框架实现包之一后,就可以直接使用。
log4j、log4j2需要做适配。
log4j
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.27version>
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-starter-web 其中已经包含了相关日志门面和日志实现的包,并且spring boot默认是使用SLF4J作为日志门面,logback作为日志实现来记录日志;因此spring boot项目是可以直接使用日志功能的。
总结:
使用 @Slf4j 注解导入额外两个包 lombok和log4j
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
编码:
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 配置文件:
<configuration>
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"/>
<property name="log_dir" value="logs">property>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<target>System.errtarget>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<springProfile name="dev">
<pattern>${pattern}pattern>
springProfile>
<springProfile name="pro">
<pattern>%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level %msg%n pattern>
springProfile>
encoder>
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.logfile>
appender>
<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%mpattern>
layout>
encoder>
<file>${log_dir}/logback.htmlfile>
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.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}-%i.log.gzfileNamePattern>
<maxFileSize>1MBmaxFileSize>
rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>infolevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<root level="info">
<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就会自动使用项目中目前存在的日志实现框架。
排除并添加依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-loggingartifactId>
<groupId>org.springframework.bootgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
<dependency>
<groupId>com.lmaxgroupId>
<artifactId>disruptorartifactId>
<version>3.3.4version>
dependency>
resources 目录下创建 logback-spring.xml 配置文件(实现error、warn、info级别日志分开目录及文件记录,AsyncLogger 混合异步日志示例):
<Configuration status="warn" monitorInterval="5">
<properties>
<property name="LOG_HOME">logsproperty>
properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/>
Console>
<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>
<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>
<RollingFile name="rollingErrorFile" fileName="${LOG_HOME}/error.log"
filePattern="logs/$${date:yyyy-MM-dd}/error/error-%d{yyyy-MM-dd-HH-mm}-%i.log">
<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/>
<SizeBasedTriggeringPolicy size="1MB"/>
<TimeBasedTriggeringPolicy/>
Policies>
<DefaultRolloverStrategy max="30"/>
RollingFile>
<RollingFile name="rollingWarnFile" fileName="${LOG_HOME}/warn.log"
filePattern="logs/$${date:yyyy-MM-dd}/warn/warn-%d{yyyy-MM-dd-HH-mm}-%i.log">
<Filters>
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<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/>
<SizeBasedTriggeringPolicy size="1MB"/>
<TimeBasedTriggeringPolicy/>
Policies>
<DefaultRolloverStrategy max="30"/>
RollingFile>
<RollingFile name="rollingInfoFile" fileName="${LOG_HOME}/info.log"
filePattern="logs/$${date:yyyy-MM-dd}/info/info-%d{yyyy-MM-dd-HH-mm}-%i.log">
<Filters>
<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/>
<SizeBasedTriggeringPolicy size="1MB"/>
<TimeBasedTriggeringPolicy/>
Policies>
<DefaultRolloverStrategy max="30"/>
RollingFile>
Appenders>
<Loggers>
<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>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="file"/>
<AppenderRef ref="rollingErrorFile"/>
<AppenderRef ref="rollingWarnFile"/>
<AppenderRef ref="rollingInfoFile"/>
Root>
Loggers>
Configuration>