Java日志技术快速入门
日志框架
一、JUL
简介
位于 java.util.logging包下,是Java自带的日志框架,使用时不需要引用第三方类库,相对于其它框架虽然功能简单,但使用方便、学习成本低,主要应用在小型应用中。
组件介绍
- Logger:被称为记录器,应用程序通过获取Logger对象,调用其API来发布日志信息。通常被认为是访问日志系统的入口。一般使用类的权限的名对实例进行命名,其命名具有继承机制。
- Handler:被称为处理器,每个记录器都会关联一个或者一组Handler,记录器会将日志交给关联的处理器去进行相应处理,由处理器负责将日志做记录。Handler可以设置日志的输出位置。
- Filter:过滤器,根据过滤条件来判断哪些日志会被记录,哪些日志会被略过。
- Formatter:格式化组件,它负责对日志中的数据和信息进行转换和格式化,所以它决定了日志最终的输出格式。
- Level:日志输出级别,每条日志都有一个关联的级别,根据输出级别的设置,用来展现最终所呈现的日志信息。
- OFF:用来关闭日志记录
- SEVERE:错误级别
- WARNING:警告级别
- INFO:关键级别,默认
- CONFIG:配置级别
- FINE:调试级别
- FINER:普通级别
- FINEST:详细级别
- ALL:用来启用所有消息的日志记录
- 配置文件:默认为JAVA_HOME/jre/lib目录下的logging.properties。也可以通过LogManager的readConfiguration()方法来设置自己的配置文件。
入门案例
import java.io.IOException;
import java.util.logging.*;
public class Demo {
public static void main(String[] args) throws IOException {
Logger log = Logger.getLogger("com.meizu.Demo");
//关闭默认日志处理器
log.setUseParentHandlers(false);
//创建控制台处理器对象
ConsoleHandler consoleHandler = new ConsoleHandler();
//创建文件处理器对象
FileHandler fileHandler = new FileHandler("/Users/meizu/Desktop/demo.log");
//创建日志格式化组件对象
SimpleFormatter formatter = new SimpleFormatter();
//在处理器中设置输出格式组件
consoleHandler.setFormatter(formatter);
fileHandler.setFormatter(formatter);
//在日志对象中添加处理器
log.addHandler(consoleHandler);
log.addHandler(fileHandler);
//设置日志的输出级别
log.setLevel(Level.FINE);
//设置处理器的级别
// 当日志输出级别和处理器级别不同时,日志级别高的生效
consoleHandler.setLevel(Level.INFO);
fileHandler.setLevel(Level.ALL);
//打印日志
log.severe("错误日志信息");
log.warning("警告日志信息");
log.info("关键日志信息");
log.config("配置日志信息");
log.fine("调试日志信息");
log.finer("普通日志信息");
log.finest("详细日志信息");
}
}
运行以上代码,我们可以观察到控制台输出的日志为INFO级别以上的信息
日志文件输出的日志则是FINE级别以上的信息:
以上我们都是采用硬编码的形式来进行配置,但是在平时的学习和工作中我们一般都不会采取这种方式来进行日志的管理,而是采用配置文件的方式来进行日志管理。
接下来我们就采用配置文件的方式来进行日志的管理演示。
配置文件内容为:
# 设置处理器为控制台处理器和文件处理器
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# 设置日志级别为
.level= CONFIG
# 设置输出文件的路径(%h 代表当前用户的家目录,%u 代表日志文件的序号)
java.util.logging.FileHandler.pattern = %h/Desktop/java%u.log
# 设置日志文件最大为5万字节
java.util.logging.FileHandler.limit = 50000
# 设置日志文件的数量
java.util.logging.FileHandler.count = 1
# 设置日志文件的输出格式为简单格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 设置文件处理器日志级别为WARNING
java.util.logging.FileHandler.level = WARNING
# 设置日志文件以追加的方式进行输出
java.util.logging.FileHandler.append = true
# 设置控制台处理器日志级别为INFO
java.util.logging.ConsoleHandler.level = INFO
# 设置控制台输出格式为XML格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.XMLFormatter
案例代码:
import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.*;
public class Demo {
public static void main(String[] args) throws IOException {
Logger log = Logger.getLogger("log.demo.Demo");
//获取配置文件的输入流
FileInputStream input = new FileInputStream("/Users/meizu/IdeaProjects/demo/module1/src/log-config.properties");
//获取日志管理器对象
LogManager logManager = LogManager.getLogManager();
//读取配置文件
logManager.readConfiguration(input);
//第二种输出日志的方式
log.log(Level.SEVERE,"错误日志信息");
log.log(Level.WARNING,"警告日志信息");
log.log(Level.INFO,"默认日志信息");
log.log(Level.CONFIG,"配置日志信息");
log.log(Level.FINE,"调试日志信息");
log.log(Level.FINER,"普通日志信息");
log.log(Level.FINEST,"详细d日志信息");
}
}
运行程序,我们会发现日志就会按照我们配置的内容来输出信息。
控制台输出内容以xml的形式输出:
日志文件内容输出:
二、Log4j
简介
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
官网:http://logging.apache.org/log4j/1.2/
组件介绍
- Loggers:日志记录器,控制日志的级别和处理日志的输出。日志级别分为:
- OFF:关闭所有日志记录
- FATAL:严重错误,一旦发生程序就无法继续运行
- ERROR:一般错误,发生的错误不影响程序的运行
- WARN:警告信息,可能会引起错误的警告信息
- INFO:关键信息
- DEBUG:调试信息,默认
- TRACE:详细信息
- ALL:用来打开所有级别的日志记录
- Appenders:输出控制器,用来指定日志的输出方式。常用的控制器有:
- ConsoleAppender:将日志输出到控制台。
- FileAppender:将日志输出到文件。
- DailyRollingFileAppender:把日志每天输出为一个新的文件。
- RollingFileAppender:将日志输出为指定大小的文件。
- JDBCAppender:将日志信息保存到数据库中。
- Loyout:日志格式化器,控制日志输出的形式。常用的格式化器有:
- HTMLLayout:将日志格式输出为HTML表格的形式。
- SimpleLayout:简单的日志格式(级别+日志信息)
- PatternLayout:可以根据自定义格式输出日志,如果没有指定转换格式,就是使用默认的转换格式。
输出格式占位符
使用PatternLayout可以自定义格式输出,是我们最常用的方式。这种格式化输出采用类似于C语言的printf函数的打印格式来格式化日志的信息,具体的占位符含义如下:
- %m:指定的日志信息
- %p:日志的级别
- %n:换行符
- %r:从应用启动到输出该日志信息消耗的毫秒数
- %c:输出该日志所属的类的全名
- %t:产生该日志的线程名
-%d:服务器的时间,默认格式为ISO8601,可以自定义格式化 - %l:日志产生的位置,包括类名、线程、代码中的行数
- %F:产生日志的文件名
- %L:代码中的行号
入门案例
要使用Log4j,首先我们需要在项目中引入相应的jar包:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
其次在配置文件log4j.properties中设置相关的属性,这里我给大家一个配置模板,里面涵盖了日常工作和学习中常用的设置,大家可以根据直接使用,也可以根据需要对相应的属性进行自定义设置:
# 配置rootLogger的日志级别和使用的输出控制器
# 该配置可以配置多个Appender,用逗号(英文)隔开
# 第一个值为输出日志的级别,之后的都为配置的自定义Appender,定义的Appender只有在这里配置后才会生效
log4j.rootLogger=trace,myAppender,toFile
# 配置myAppender的输出控制器
log4j.appender.myAppender=org.apache.log4j.ConsoleAppender
# 配置myAppender的格式化器
log4j.appender.myAppender.layout=org.apache.log4j.PatternLayout
# 配置日志的输出格式
log4j.appender.myAppender.layout.conversionPattern=[%-5p]%d{yyyy-MM-dd HH: mm:ss} %c>%t : %m%n
# 配置toFile的输出控制器
log4j.appender.toFile=org.apache.log4j.FileAppender
log4j.appender.toFile.layout=org.apache.log4j.PatternLayout
log4j.appender.toFile.layout.conversionPattern=[%-5p]%d{yyyy-MM-dd HH: mm:ss} %c|%t ==> %m%n
# 配置toFile的日志文件输出位置
log4j.appender.toFile.file=/Users/meizu/Desktop/java.log
# 配置文件输出的编码
log4j.appender.toFile.encoding=UTF-8
# 通过大小拆分日志文件的相关配置
log4j.appender.toFileBySize=org.apache.log4j.RollingFileAppender
log4j.appender.toFileBySize.layout=org.apache.log4j.PatternLayout
log4j.appender.toFileBySize.layout.conversionPattern=[%-5p]%d{yyyy-MM-dd HH: mm:ss} %c|%t ==> %m%n
log4j.appender.toFileBySize.file=/Users/meizu/Desktop/java.log
log4j.appender.toFileBySize.encoding=UTF-8
# 日志文件的内容的大小
log4j.appender.toFileBySize.maxFileSize=1MB
# 指定日志文件的数量
log4j.appender.toFileBySize.maxBackupIndex=5
# 通过日期拆分文件的相关配置
log4j.appender.toFileByDate=org.apache.log4j.DailyRollingFileAppender
log4j.appender.toFileByDate.layout=org.apache.log4j.PatternLayout
log4j.appender.toFileByDate.layout.conversionPattern=[%-5p]%d{yyyy-MM-dd HH: mm:ss} %c|%t ==> %m%n
log4j.appender.toFileByDate.file=/Users/meizu/Desktop/java.log
log4j.appender.toFileByDate.encoding=UTF-8
# 设置拆分文件的精度,这里的精度设置为分钟,一般精度为天
log4j.appender.toFileByDate.datePattern='.'yyyy-MM-dd HH-mm
# 设置将日志保存到数据库的相关配置
log4j.appender.toDB=org.apache.log4j.DailyRollingFileAppender
log4j.appender.toDB.layout=org.apache.log4j.PatternLayout
log4j.appender.toDB.layout.conversionPattern=[%-5p]%d{yyyy-MM-dd HH: mm:ss} %c|%t ==> %m%n
# 设置数据库的驱动
log4j.appender.toDB.Driver=com.mysql.jdbc.Driver
# 设置连接数据库的URL
log4j.appender.toDB.URL=jdbc://localhost:3306/test
# 用户名
log4j.appender.toDB.User=root
# 密码
log4j.appender.toDB.Password=123456
# 写入日志的sql语句,需要提前创建好对于的表。注意:这个sql语句不能换行,需要写在一行
log4j.appender.toDB.Sql=INSERT INTO java_log(class_name,time,level,file,message) values('%c','%d{yyyy-MM-dd HH : mm: ss}','%p','%F','%m')
#自定义Logger,并指定日志级别和使用的输出控制器
log4j.logger.com.meizu.xxx=info,myAppender
接下来只需要获取日志记录器就可以在需要输出日志的地方输出对应的日志信息:
import org.apache.log4j.Logger;
import org.junit.Test;
public class MyTest {
@Test
public void log4jTest(){
//获取日志记录器
Logger logger = Logger.getLogger(MyTest.class);
logger.fatal("fatal信息");
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
}
我们运行程序,就可以观察到在控制台就会输出对应的日志信息:
同样的,生成的日志文件也会有相应的信息:
三、logback
简介
logback是由Log4j创始人设计的有一个开源日志组件,是目前使用最广泛的Java日志框架。
官网:https://logback.qos.ch/
模块介绍
- logback-core:是其他模块的基础与核心。
- logback-cassic:是Log4j的一个改良版本,完整实现了SLF4J API(后面要讲的日志门面框架)并提供了许多新特性。
- logback-access:访问模块与Servlet容器集成,提供通过HTTP来访问日志的功能。
组件介绍
- Logger:日志记录器,主要用于存放日志对象,也可以定义日志类型、级别。logback对日志级别的划分为5个等级:
- error:错误信息
- warn:警告信息
- info:关键信息
- debug:调试信息,默认
- trace:详细信息
- Appender:输出控制器,用于指定日志输出的方式。
- Layout:格式化器,设置日志信息的格式。被封装在encoder组件中。
输出格式占位符
- %level:日志级别
- %d:服务器时间
- %c:类的全限定名
- %C:类名
- %M:执行代码的方法名
- %L:代码所在的行号
- %thread:线程名
- %m或%msg:日志信息
- %n:换行符
入门案例
首先依然是导入相关的依赖:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
由于logback-classic已经依赖了logback-core,所以在我们导入logback-classic时Maven的依赖传递性会自动导入logback-core的依赖,由于logback完整实现了SLF4J API所以同时也会导入SLF4J的依赖(这里不知道SLF4J是什么也没关系,后面日志门面会详细讲解):
接下来就是在配置文件中对相关的属性进行设置,logback提供了三种格式的配置文件:logback.groovy、logback-test.xml、logback.xml,我们一般使用logback.xml进行配置,这里我同样给大家一个通用模板,大家可以在实际开发中根据自己的需要进行设置。这里需要强调一点,在开发中没有声明了但是没有使用的会自动生成文件的Appender一定要注释掉(尤其是HTML文件),否则它会在首次运行时产生文件,HTML还会在每次运行时向文件中追加运行信息和样式。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- 配置日志信息的格式 -->
<property name="myFormat" value="%d{HH:mm:ss.SSS} [%thread] %level %C - %m%n"/>
<!-- 配置日志文件的输出路径 -->
<property name="logPath" value="/Users/meizu/Desktop"/>
<!-- 配置控制台输出控制器 -->
<appender name="toConsole" class="ch.qos.logback.core.ConsoleAppender">
<!-- 设置日志在控制台输出的颜色:System.out(黑色,默认)、System.err(红色) -->
<target>System.out</target>
<!-- 设置日志的Layout -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 引用上面定义的日志信息格式 -->
<pattern>${myFormat}</pattern>
</encoder>
</appender>
<!-- 配置普通文件输出控制器 -->
<appender name="toFile" class="ch.qos.logback.core.FileAppender">
<!-- 引入文件位置 -->
<file>${logPath}/java.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${myFormat}</pattern>
</encoder>
</appender>
<!-- 配置HTML文件输出控制器 -->
<appender name="toHTML" class="ch.qos.logback.core.FileAppender">
<file>${logPath}/log.html</file>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<!-- 设置需要记录的属性,尽量不要出现占位符之外其他字符,包括空格 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss}%thread%level%c%m</pattern>
</layout>
</encoder>
</appender>
<!-- 配置拆分文件输出控制器 -->
<appender name="toFileSplit" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${myFormat}</pattern>
</encoder>
<!-- 设置文件拆分的规则 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 按照时间和压缩格式声明文件名 采用gz格式进行压缩 -->
<fileNamePattern>${logPath}/log_%i.%d{yyyy-MM-dd}.gz</fileNamePattern>
<!-- 设置拆分文件的大小 -->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!-- 配置控制台输出使用过滤器(其他输出控制器也可以使用过滤器,这里只是以ConsoleAppender为例) -->
<appender name="byFilter" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${myFormat}</pattern>
</encoder>
<!-- 配置过滤器 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置日志输出的级别 -->
<level>warn</level>
<!-- 设置当日志级别与level匹配时的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 设置当日志级别与level不匹配时的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 配置异步日志 -->
<appender name="byAsync" class="ch.qos.logback.classic.AsyncAppender">
<!-- 引入需要异步执行的Appender -->
<appender-ref ref="toConsole"/>
</appender>
<!-- 配置rootLogger的日志的级别和使用的输出控制器 -->
<!-- 声明的appender只有在这里设置了才会生效 -->
<root level="trace">
<!-- 设置使用的输出控制器,可配置多个 -->
<appender-ref ref="toConsole"/>
<appender-ref ref="toHTML"/>
</root>
<!-- 自定义Logger -->
<!-- additivity="false"表示不继承rootLogger -->
<logger name="com.meizu.xxx" level="info" additivity="false">
<appender-ref ref="toConsole"/>
</logger>
</configuration>
接着我们就可以使用SLF4J的日志工厂获取日志记录器,输出相关的日志信息:
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyTest {
@Test
public void test1(){
//通过日志工厂获取日志对象
Logger logger = LoggerFactory.getLogger(MyTest.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
}
运行程序,我们就会发现控制台上输出了对应的日志信息:
同时在对应的目录下也生成了日志HTML文件:
四、Log4j2
简介
Log4j2是对Log4j的升级,它在Log4j的基础上进行了重大改进,并借鉴了Logback中许多优秀的功能,同时修复了Logback架构中的一些问题。Log4j2本身也有自己的日志门面,但是并不足以取代目前最主流的日志门面SLF4J,同时由于Log4j2的日志实现功能非常强大、性能优越,是目前功能最强大的日志实现(没有之一),所以我们一般情况下还是将Log4j2看做是日志实现。另外还有一点需要注意:由于Log4j2发现了远程代码执行漏洞,所以我们在使用时一定要使用2.15.0以上的版本。
官网:http://logging.apache.org/log4j/2.x/
特性
- 性能优异:Log4j2包含基于LMAX Disruptor库的下一代异步记录器。在多线程场景中,异步记录器的吞吐量比Log4j和Logback高18倍左右,延迟低。
- 自动重新加载配置:与Logback一样,Log4j2可以再修改时自动重新加载其配置。与Logback不同,它会在重新配置发生时不会丢失日志事件。
- 高级过滤:Log4j2支持基于Log事件中的上下文数据、标记、正则表达式和其他组件进行过滤。此外,过滤器还可以与记录器关联。Log4j2还可以在任何情况下使用通用的Filter类。
- 插件架构:Log4j2使用插件模式配置组件。因此,使用时无需编写代码来创建和配置Appender、Layout、Pattern Converter等。在配置了的情况下,Log4j2会自动识别并使用它们。
- 无垃圾机制:在稳定日志记录期间,Log4j2在独立应用程序中时无垃圾的,在Web应用程序中是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的相应性能。
入门案例
为了避免远程代码执行的漏洞,这里我们使用2.16.0版本的依赖:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.16.0</version>
</dependency>
我们首先使用Log4j2的门面+实现来进行日志的输出:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class MyTest {
@Test
public void test1(){
Logger logger = LogManager.getLogger(MyTest.class);
logger.fatal("fatal信息");
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
}
运行程序,我们会发现默认的日志级别为error:
和其他日志框架一样,如果需要设置日志的级别或输出方式,同样需要使用配置文件来进行配置。这里同样给大家一个模板,大家根据自己的需要来进行自定义配置:
<?xml version="1.0" encoding="utf-8" ?>
<!-- 配置文件根标签,有两个属性:status="" 用来配置框架本身的日志级别、monitorInterval="" 指定自动加载配置文件的最大间隔时间(单位秒) -->
<Configuration>
<!-- 配置通用属性 -->
<Properties>
<!-- 配置日志文件的输出路径 -->
<Property name="logPath">/Users/meizu/Desktop</Property>
</Properties>
<!-- 配置Appender -->
<Appenders>
<!-- 配置控制台输出,name为标识,target指定输出的颜色(SYSTEM_OUT为黑色,SYSTEM_ERR为红色) -->
<Console name="toConsole" target="SYSTEM_ERR">
<!-- 指定日志输出格式 -->
<PatternLayout pattern="[%-5p]%d{yyyy-MM-dd HH:mm:ss} %c > %t : %m%n" />
</Console>
<!-- 配置文件输出,createOnDemand表示是否在需要时才创建文件,默认false -->
<File name="toFile" fileName="${logPath}/log4j2.log" createOnDemand="true">
<PatternLayout pattern="[%-5p]%d{yyyy-MM-dd HH:mm:ss} %c->%t : %m%n" />
</File>
<!-- 随机读写流的日志输出 -->
<!-- 注意:FileName中的文件名不能和其他Appender重复,否则会报错 -->
<RandomAccessFile name="toRandom" fileName="${logPath}/random.log">
<PatternLayout pattern="[%-5p]%d{yyyy-MM-dd HH:mm:ss} %c->%t : %m%n" />
</RandomAccessFile>
<!-- 配置拆分日志文件输出,filePattern时日志文件拆分后文件的命名规则 -->
<!-- $${date:yyyy-MM-dd}:表示根据当天日期创建一个文件夹,拆分出来的日志会被放入这个文件夹 -->
<RollingFile name="toRollingFile" fileName="${logPath}/rolling.log" createOnDemand="true"
filePattern="${logPath}/$${date:yyyy-MM-dd}/log4j2-%d{yyyy-MM-dd-HH-mm}-%i.log">
<PatternLayout pattern="[%-5p]%d{yyyy-MM-dd HH:mm:ss} %c->%t : %m%n" />
<!-- 配置拆分规则 -->
<Policies>
<!-- 在系统启动时触发拆分规则,产生一个日志文件 -->
<OnStartupTriggeringPolicy/>
<!-- 按照文件大小进行拆分 -->
<SizeBasedTriggeringPolicy size="1KB" />
<!-- 按照filePattern指定的时间节点进行拆分 -->
<TimeBasedTriggeringPolicy/>
</Policies>
<!-- 指定同一个目录下的文件个数限制,如果超出数量限制则新的覆盖旧的 -->
<DefaultRolloverStrategy max="10"/>
</RollingFile>
<!-- 配置异步日志输出(此方式不常用,了解即可) -->
<Async name="byAsync">
<!-- 配置需要异步的Appender -->
<AppenderRef ref="toConsole"/>
</Async>
</Appenders>
<!-- 配置Logger -->
<Loggers>
<!-- 配置root日志记录器 -->
<Root level="trace">
<!-- 指定使用的Appender -->
<AppenderRef ref="toConsole" />
<AppenderRef ref="toFile" />
</Root>
<!-- 配置自定义异步Logger -->
<!-- name属性指定需要使用异步方式进行日志记录的目录 -->
<!-- includeLocation="false"表示去除日志输出中的行号信息,因为严重影响执行效率 -->
<!-- additivity="false"表示不继承父Logger -->
<AsyncLogger name="com.meizu.xxx" level="debug" includeLocation="false" additivity="false">
<AppenderRef ref="toConsole"/>
</AsyncLogger>
</Loggers>
</Configuration>
这里有一点需要强调:Log4j2的异步分为AsyncAppender和AsyncLogger两种方式,其中AsyncAppender时通过引用别的Appender来实现的,当有日志事件到达时,会开启另一个线程来处理,如果Appender在运行时出现异常,应用是无法感知的,所以一般不会使用这种方式。AsyncLogger才是实现异步最重要的功能体现,也是官方推荐的异步方式,这种方式又可以分为全局异步(所有的日志都使用异步方式进行记录)和混合异步(只有指定目录下的日志记录为异步方式,其余目录则为同步方式)两种。全局异步不需要改变原有的配置文件和代码,只需要在resources目录下添加一个名为log4j2.component.properties,内容为:
Log4jContextSelector=org.apache.logger.log4j.core.async.AsyncLoggerContextSelector
的配置文件即可。混合异步则需要首先把log4j2.component.properties配置文件删除掉或把内容注释掉(否则还是全局异步),然后在pom.xml中导入一个disruptor的依赖:
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.1</version>
</dependency>
之后在log4j2.xml配置文件的
编写好了配置文件之后,我们再来运行程序,就会发现输出的日志信息级别就变成了配置文件中配置的级别:
同时也生成了对应的日志文件:
日志门面技术
以上介绍的日志框架技术,都可以用来有效的记录日志信息。但是它们每一种日志框架都有自己单独的API,要使用对应的框架就要使用对应的API,这就大大的增加了应用程序代码对于日志框架的耦合性。如果我们的工程中引入了其他的工程,但是其它的工程使用的不是我们工程所使用的框架,那么在进行日志维护时就会非常的麻烦。因此我们就需要一种技术,将不同的日志框架共有的特征行为进行抽取形成接口,来使得无论底层的日志框架如何实现,对于使用者来说都是同一套API,则我们称这样的技术为日志门面技术。
日志门面使用GoF23中设计模式之一的门面模式(Facade Pattern)进行实现,也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。
一、JCL
简介
JCL全称为Jakarta Commons Logging,是Apache提供的一个通用日志API。用户可以自由选择第三方的日志框架组件(Log4j、JUC)作为具体实现,当然它也有自己的简单实现,不过功能太过简单所以一般不会使用。common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。
基本抽象类
- Log:日志记录器
- LogFactory:日志工厂(负责创建Log实例)
入门案例
首先我们引入JCL的相关依赖:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
然后我们就可以通过日志工厂获取一个日志记录器,通过日志对象我们来输出一个简单的日志信息:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
public class MyTest {
@Test
public void log4jTest(){
//通过日志工厂获取日志记录器
Log log = LogFactory.getLog(MyTest.class);
//输出一个简单的日志信息
log.info("Info 信息 ~ ~");
}
}
运行程序,我们就可以观察到在我们没有引入Log4j时,默认会使用JUL来进行记录日志的操作:
接着我们导入Log4j的相关依赖,并将上面的log4j.properties配置文件拿过来。不用改变任何代码,运行程序,通过控制台输出的信息就可以观察到,已经无缝切换到Log4j了:
通过上面的案例,我们就会发现:使用JCL进行日志记录时,在没有引入其它第三方日志框架时,使用的时JUL来进行日志的记录,但是在集成了Log4j后,虽然代码完全没有改变,但是日志记录的操作却变成了由log4j来完成。由此我们就体会到了日志门面面向接口的开发不再依赖具体的实现类,减少代码的耦合性。可以根据实际需求灵活的切换日志框架,统一的API方便开发者学习、使用和维护。但是由于实现JCL的日志框架较少且功能有限,所以一般的开发中我们并不会使用JCL来进行开发,而是使用接下来要讲的功能更强大的日志门面框架SLF4J来进行开发。
二、SLF4J
简介
SLF4J全称为Simple Logging Facadde For Java,主要是为了给Java日志访问提供了一套标准、规范的API框架,其主要意义在于提供接口,具体的实现有其他日志框架如Log4j、Logback等,当然SLF4J也提供了功能较为简单的实现(需要单独导入),但是一般很少用到。官网:www.slf4j.org
SLF4J对日志级别的划分为5个等级:
- error:错误信息
- warn:警告信息
- info:关键信息,默认
- debug:调试信息
- trace:详细信息
SLF4J桥接技术
由于SLF4J出现的较晚,对于出现相对较早的Log4j、JUL、JCL来说并没有按照SLF4J的API标准来实现。所以为了让使用这些较早出现的日志框架的项目可以在不改变任何代码的情况下无缝切换为新的日志框架或由SLF4J来进行管理,因此SLF4J推出了对应的桥接模块,这些模块会将那些出现较早的日志框架的API调用重定向为行为,就好像是对SLF4J API进行的操作一样。
入门案例
首先我们先导入SLF4J核心的相关依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
然后通过日志工厂获取日志记录器,打印相关日志信息:
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyTest {
@Test
public void test1(){
//通过日志工厂获取日志记录器
Logger logger = LoggerFactory.getLogger(MyTest.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
}
可以看到出现了相关的异常信息:
这是由于SLF4J核心并没有提供任何的日志实现,需要我们导入相关的日志依赖才可以正常工作,这点在官方提供的架构图中就可以看出来:
那我们就按照官方的要求导入Log4j的依赖,同时将上面的log4j.properties配置文件也拿过来,这里有一点需要注意,那就是由于Log4j出现的较早,所以SLF4J为了适配Log4j提供了一个适配器,我们需要在导入Log4j时连同这个适配器一起导入:
<!-- log4j的适配器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
导入之后我们再来运行程序,就可以观察到日志按照我们配置文件的设置正常输出了:
当然,我们也可以导入其他的日志框架来进行日志的管理,这里我们将Log4j切换为logback来看看会是什么样的效果:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 将Log4j的依赖注释掉 -->
<!--<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>-->
运行程序,我们就会发现程序就无缝的切换为logback了:
到这里我们可能会有一个疑问:刚刚是在注释掉Log4j依赖的情况下运行的,那如果我们同时引入logback和Log4j会出现什么样的情况呢?
那我们就来尝试一下:
运行程序,我们会发现,虽然程序使用logback正常的输出了日志,但同时也出行了一些警告信息:
这是因为SLF4J同时只能使用一个日志实现来进行信息的输出,但它却检测到了多个实现,加载时栈溢出导致的。
那为什么程序使用的是logback,而不是Log4j呢?
这是因为SLF4J会按照pom文件中导入的顺序进行加载,logback在Log4j之前导入,所以使用的是logback。我们也可以把Log4j放到前面,来观察日志的输出情况:
运行程序,这时就会发现日志信息是按照log4j配置文件中定义的格式来输出的:
由此我们可以得出以下结论:
- SLF4J核心并没有提供任何日志实现,在没有导入相关日志实现框架时无法正常工作。
- 当同时存在多个日志实现框架时,SLF4J默认会使用pom文件中最先导入的框架进行日志的输出。
- 我们在平时使用时只需导入一种日志实现框架即可,否则会出现栈溢出的警告。
最后,我们再来演示如何使用SLF4J整合Log4j2,由于Log4j2有自己的日志门面,所以SLF4J在调用Log4j2的相关功能时并不能直接调用实现,而是先调用Log4j2的日志门面,再由Log4j2的日志门面去调用对应的日志实现,因此Log4j2为我们提供了相关的适配器。所以我们需要在导入SLF4J和Log4j2日志实现的同时还需要导入Log4j2的日志门面和适配器的依赖:
<!-- Log4j2的日志实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.15.0</version>
</dependency>
<!-- Log4j2的日志门面 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.15.0</version>
</dependency>
<!-- Log4j2的适配器 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.15.0</version>
</dependency>
然后再将上面的log4j2.xml配置文件拿过来,运行程序,就会发现控制台输出了使用Log4j2打印的日志信息:
同时也输出了对应的日志文件:
至此,我们就完成了SLF4J和主流日志框架的整合。
结语
虽然上面整理了目前市面上常见的日志框架和日志门面技术,并且给出了详细的代码、日志文件及运行截图,但是都属于入门级别的案例。写这篇博客的目的就会为了让大家可以快速的学习和了解主流的日志框架技术,所以只讲解了实际开发中比较常用的部分,如果大家想要学习相关日志技术的详细使用方法,最好的教程还是官方的说明文档,博主有附上相关框架的官方网站。
最后,博主自知写作水平有限,内容不够详细,还希望大家多多包涵。