Apache Log4j2 是对Log4j 的升级版本,参考了logback 的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:
官网:https://logging.apache.org/log4j/2.x/
目前市面上最主流的日志门面就是SLF4J,虽然Log4j2 也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将 Log4j2 看作是日志的实现,Slf4j + Log4j2 应该是未来的大势所趋。
1.添加依赖
<dependencies>
<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>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
2.编写代码
package com.log;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class Log4j2Test {
// 定义日志记录器对象
public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
// 快速入门
@Test
public void testQuick() throws Exception {
// 日志消息输出
LOGGER.fatal("fatal");
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
3.运行结果截图
上述报错是因为没有找到日志配置文件,需要添加 log4j2.xml 配置文件到 resource 目录下【classpath】下。log4j2 默认加载classpath 下的 log4j2.xml 文件中的配置
<configuration status="warn" monitorInterval="5">
<properties>
<property name="LOG_HOME">E:/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="rollingFile" fileName="${LOG_HOME}/myrollog.log"
filePattern="E:/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="10 MB" />
<TimeBasedTriggeringPolicy />
Policies>
<DefaultRolloverStrategy max="30" />
RollingFile>
Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Console" />
Root>
Loggers>
configuration>
实际生产中,我们往往需要 slf4j + log4j2 进行日志管理;就需要导入slf4j 日志门面、log4j2 适配器;然后使用 slf4j 方法接口名称来输出日志
<dependencies>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.26version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>2.9.1version>
dependency>
<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>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
编写 slf4j + log4j2 代码
package com.log;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jTest {
// 为了保证使用时,不需要每次都去创建logger 对象,我们声明静态常量
public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
// 快速入门
@Test
public void testQuick(){
// 日志输出
LOGGER.error("error");
LOGGER.warn("warning");
LOGGER.info("info"); // 默认的日志级别信息
LOGGER.debug("debug");
LOGGER.trace("trace"); // 追踪信息
// 使用占位符输出日志信息
String name = "java_log";
Integer age = 18;
LOGGER.info("用户:{},{}", name, age);
// 将系统的异常信息输出
try {
int i = 1 / 0;
} catch (Exception e){
// e.printStackTrace();
LOGGER.error("出现异常:", e);
}
}
}
log4j2 vs slf4j + log4j2 日志输出对比:
log4j2 默认加载classpath 下的 log4j2.xml 文件中的配置。下面通过log4j2.xml 配置文件进行测试
<configuration status="warn" monitorInterval="5">
<properties>
<property name="LOG_HOME">D:/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="rollingFile" fileName="${LOG_HOME}/myrollog.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd}-%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="2MB" />
<TimeBasedTriggeringPolicy />
Policies>
<DefaultRolloverStrategy max="10" />
RollingFile>
Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="file" />
<AppenderRef ref="rollingFile" />
<AppenderRef ref="accessFile" />
Root>
Loggers>
configuration>
编写代码:
package com.log;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jTest {
// 为了保证使用时,不需要每次都去创建logger 对象,我们声明静态常量
public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
// 快速入门
@Test
public void testQuick(){
// 日志输出
for (int i = 0; i < 1000000; i++) {
LOGGER.error("error");
LOGGER.warn("warning");
LOGGER.info("info"); // 默认的日志级别信息
LOGGER.debug("debug");
LOGGER.trace("trace"); // 追踪信息
}
// 使用占位符输出日志信息
/*String name = "java_log";
Integer age = 18;
LOGGER.info("用户:{},{}", name, age);
// 将系统的异常信息输出
try {
int i = 1 / 0;
} catch (Exception e){
// e.printStackTrace();
LOGGER.error("出现异常:", e);
}*/
}
}
运行结果截图:
异步日志
log4j2 最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益,我们来看看如何使用log4j2 的异步日志。
Log4j2 提供了两种实现日志的方式,一个是通过AsyncAppender【几乎没人用】,一个是通过AsyncLogger【主要是这个】,分别对应前面我们说的Appender 组件和Logger 组件。
官网详细介绍:http://logging.apache.org/log4j/2.x/performance.html
注意:配置异步日志需要添加依赖
<dependency>
<groupId>com.lmaxgroupId>
<artifactId>disruptorartifactId>
<version>3.3.4version>
dependency>
1、AsyncAppender 方式【生产上几乎不使用,因为性能低下】
<configuration status="warn" monitorInterval="5">
<properties>
<property name="LOG_HOME">D:/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 %p %c{1.} [%t] %m%n" />
File>
<Async name="Async">
<AppenderRef ref="file" />
Async>
Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Console" />
<AppenderRef ref="Async" />
Root>
Loggers>
configuration>
2、编写代码
package com.log;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jTest {
// 为了保证使用时,不需要每次都去创建logger 对象,我们声明静态常量
public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
// 快速入门
@Test
public void testQuick(){
// 日志输出
LOGGER.error("error");
LOGGER.warn("warning");
LOGGER.info("info"); // 默认的日志级别信息
LOGGER.debug("debug");
LOGGER.trace("trace"); // 追踪信息
}
}
3、运行结果截图,下面就是异步日志输出
2、AsyncLogger 方式【生产上用得多,因为性能高】
AsyncLogger 才是log4j2 的重头戏,也是官方推荐的异步方式。它可以调用Logger.log 返回的更快。你可以有两种选择:全局异步和混合异步。
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
则此时,向控制台、文件都是异步方式日志输出
<configuration status="debug" monitorInterval="5">
<properties>
<property name="LOG_HOME">D:/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 %p %c{1.} [%t] %m%n" />
File>
<Async name="Async">
<AppenderRef ref="file" />
Async>
Appenders>
<Loggers>
<AsyncLogger name="com.log" level="trace" includeLocation="false" additivity="false">
<AppenderRef ref="Console" />
AsyncLogger>
<Root level="trace">
<AppenderRef ref="Console" />
<AppenderRef ref="Async" />
Root>
Loggers>
configuration>
同时,AsyncLogger 混合异动日志输出需要将log4j2.component.properties 内容进行注释【因为这个是配置AsyncLogger全局异步日志输出】
如下配置:com.log 日志是异步的, root 日志是同步的。
使用异步日志需要注意的问题:
Log4j2 最牛的地方在于异步输出日志时的性能表现,Log4j2 在多线程的环境下吞吐量与 Log4j 和 Logback 的比较如下图。下图比较中 Log4j2 有三种模式:
可以看出在前两种模式下,Log4j2 的性能较之 Log4j 和Logback有很大的优势。
无垃圾记录
垃圾收集暂停是延迟峰值的常见原因,并且对于许多系统而言,花费大量精力来控制这些暂停。
许多日志库(包括以前版本的Log4j)在稳态日志记录期间分配临时对象,如日志事件对象,字符串,字符数组,字节数组等。这会对垃圾收集器造成压力并增加 GC 暂停发生的概率。
从版本2.6 开始,默认情况下 Log4j 以“无垃圾” 模式运行,其中重用对象和缓冲区,并且尽可能不分配临时对象。还有一个“低垃圾”模式,它不是完全无垃圾,但不使用ThreadLocal 字段。
Log4j 2.6 中的无垃圾日志记录部分通过重用ThreadLocal 字段中的对象来实现,部分通过在将文件转换为字节时重用缓冲区来实现。
使用Log4j 2.5:内存分配速度809 MB / 秒,141个无效集合。
Log4j 2.6没有分配临时对象:0(零)垃圾回收。
有两个单独的系统属性可用于手动控制Log4j 用于避免创建临时对象的机制:
真诚的建议:如果您的应用程序是多线程的并且日志记录性能很重要,请考虑使用异步记录器。