Log4J2 详细介绍

Posted CoderLi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Log4J2 详细介绍相关的知识,希望对你有一定的参考价值。

概述

Log4J 是 Java 编写的可靠的、快速灵活的日志框架 。可根据配置将日志信息输送到不同的目的地、比如数据库、文件、控制台、Unix 系统日志等等

Log4J 的主要组成部分

  • loggers 负责捕获记录信息

  • appenders 负责输送日志到不同的目的地

  • layouts 负责格式化日志

Log4J的特性

  • 线程安全的

  • 基于一个名为记录器的层次结构

  • 支持每个记录器有多个appender

  • 多个日志级别 OFF、ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL

使用

<properties>
<log4j.version>2.5</log4j.version>
<disruptor.version>3.3.2</disruptor.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* @author ljx
*/

public class Main {
private final static Logger logger = LogManager.getRootLogger();
public static void main(String[] args) {
logger.error("error");
System.out.println("name:" + logger.getName());
System.out.println("level:" + logger.getLevel());
}
}
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.
16:58:01.957 [main] ERROR - error
name:
level:ERROR
Disconnected from the target VM, address: '127.0.0.1:42151', transport: 'socket'

Log4J有默认的配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

配置

  1. 根节点

  2. Appenders 节点

  3. Loggers 节点

根节点

有两个比较重要的属性 status 和 monitorinterval

status 用来指定log4j 本身打印日志的级别

monitorinterval 指定log4j 自动重新检测配置文件是否改变然后重新加载的时间讲过、单位为秒、必须大于0

常见的配置方式有xml,yaml,json。与之对应的类就是XmlConfiguration、JsonConfiguration....

Appenders 节点

Log4J2 详细介绍

常用的节点有Console、RollingFile、File。常见属性

Console 节点

对应的 Java 类 就是 ConsoleAppender

  • name 指定Appender的名字

  • target 默认是 SYSTEM_OUT、可以设置为 SYSTEM_ERR | SYSTEM_OUT 位于 ConsoleAppender中的内部枚举  Target

  • PatternLayout 输出格式 默认为 %m%n 只打印应用程序提供的信息

File 节点

对应的Java类就是  FileAppender

  • name 指定Appender的名字.

  • fileName 指定输出日志的目的文件带全路径的文件名.

  • PatternLayout 输出格式,不设置默认为:%m%n.

  • append 是否追加当旧日志文件中、false的话就会清空原来的日志文件

RollingFile 节点

对应的Java类就是 RollingFileAppender

  • name 指定Appender的名字.

  • fileName 指定输出日志的目的文件带全路径的文件名.

  • PatternLayout 输出格式,不设置默认为:%m%n.

  • filePattern 指定新建日志文件的名称格式.

  • Policies 指定滚动日志的策略,就是什么时候进行新建日志文件输出日志.

    • TimeBasedTriggeringPolicy:Policies子节点,基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am.

    • SizeBasedTriggeringPolicy:Policies子节点,基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小.

    • DefaultRolloverStrategy:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)。

    • CronTriggeringPolicy corn 表达式

Loggers 节点

Log4J2 详细介绍

Root

Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出

  • level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.

  • AppenderRef:Root的子节点,用来指定该日志输出到哪个Appender.

Logger

Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。

  • level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.

  • name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点.

  • AppenderRef:Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root.如果指定了,那么会在指定的这个Appender和Root的Appender中都会输出,此时我们可以设置Logger的additivity="false"只在自定义的Appender中进行输出。第一次打印使用自己的、后续使用继承的

Level

org.apache.logging.log4j.Level
 /**
* No events will be logged.
*/

public static final Level OFF;

/**
* A severe error that will prevent the application from continuing.
*/

public static final Level FATAL;

/**
* An error in the application, possibly recoverable.
*/

public static final Level ERROR;

/**
* An event that might possible lead to an error.
*/

public static final Level WARN;

/**
* An event for informational purposes.
*/

public static final Level INFO;

/**
* A general debugging event.
*/

public static final Level DEBUG;

/**
* A fine-grained debug message, typically capturing the flow through the application.
*/

public static final Level TRACE;

/**
* All events should be logged.
*/

public static final Level ALL;

/**
* @since 2.1
*/

public static final String CATEGORY = "Level";

private static final ConcurrentMap<String, Level> LEVELS = new ConcurrentHashMap<>(); // SUPPRESS CHECKSTYLE

private static final long serialVersionUID = 1581082L;

static {
OFF = new Level("OFF", StandardLevel.OFF.intLevel());
FATAL = new Level("FATAL", StandardLevel.FATAL.intLevel());
ERROR = new Level("ERROR", StandardLevel.ERROR.intLevel());
WARN = new Level("WARN", StandardLevel.WARN.intLevel());
INFO = new Level("INFO", StandardLevel.INFO.intLevel());
DEBUG = new Level("DEBUG", StandardLevel.DEBUG.intLevel());
TRACE = new Level("TRACE", StandardLevel.TRACE.intLevel());
ALL = new Level("ALL", StandardLevel.ALL.intLevel());
}

配置方式

记录日志的方式

  • 同步日志

  • 异步日志

    • AsyncAppender

    • logger all async

同步日志

当输出日志时、必须等待日志输出完成、才会继续执行下面的代码

曾经试过在内部配置系统出、打印邮件模版、打印了20s才返回给后台、当时以为是数据量太大、后来发觉是打印日志占用了时间。

混合日志

        <AsyncLogger name="xxxx" level="INFO" additivity="false">
<AppenderRef ref="xxxx-kafka"/>
<AppenderRef ref="xxxx-file"/>
</AsyncLogger>

公司使用的就是通过log4j2 将日志推送到kafka中、然后使用flinks处理、然后写入到es中、这个当然使用的是单纯的异步去写

默认情况下、异步记录器不会将位置信息(即打印日志所在的类全路径名)传递给I/O线程的、我们需要配置includeLocation为true才会将位置信息传递给他。

2020-01-10 22:20:52,366 INFO  [Thread-3] ? - info
2020-01-10 22:20:52,369 ERROR [Thread-3] ? - error
2020-01-10 22:22:38,696 INFO [Thread-3] ? - info
2020-01-10 22:22:38,698 ERROR [Thread-3] ? - error
=================================
2020.01.10 22:22:38 CST Thread-2 INFO com.TestLog 10 lambda$main$0 - info
2020.01.10 22:22:38 CST Thread-3 INFO com.TestLog 15 lambda$main$1 - info
2020.01.10 22:22:38 CST Thread-2 ERROR com.TestLog 12 lambda$main$0 - error

Loggers all in asyn

想要使用Loggers all async还需要做一步设置,如果是Maven或Gradle项目,需要在src/main/resources目录下添加log4j2.component.properties配置文件,根据官网说明,其中内容为

Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

同时还要引入disruptor

		<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>${disruptor.version}</version>
</dependency>

其他

<Async name="asyncKafkaLog">
<AppenderRef ref="Failover" />
</Async>

注意事项:此类异步队列是BockingQueue,队列默认大小是128

<AsyncLogger name="kafkaLogger" level="trace" includeLocation="false">
<AppenderRef ref="Failover"/>
</AsyncLogger>

注意事项:此类异步队列是Disruptor队列默认大小是4096

  • Disruptor队列性能远胜于BlockingQueue,这也是log4j2性能提升的重要原因之一

  • 如果启用了全量异步,又使用了<AsyncLogger>会如何?

    • log4j2会新建两个Disruptor队列,<AsyncLogger>之流使用一个,其他的使用另外一个,所以建议将可能发生阻塞的logger归类使用一个Disruptor,毕竟是队列,一个阻塞了其他的得乖乖等着

动态加载配置文件

monitorinterval 单位是秒、如果配置大于0、则会按照时间间隔自动扫描配置文件是否被修、并在修改后重新加载文、如果不配置,默认是0、即不扫描配置文件是否被修改。

XmlConfiguration

for (final Map.Entry<String, String> entry : attrs.entrySet()) {
final String key = entry.getKey();
final String value = getStrSubstitutor().replace(entry.getValue());
if ("status".equalsIgnoreCase(key)) {
statusConfig.withStatus(value);
} else if ("dest".equalsIgnoreCase(key)) {
statusConfig.withDestination(value);
} else if ("shutdownHook".equalsIgnoreCase(key)) {
isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
} else if ("verbose".equalsIgnoreCase(key)) {
statusConfig.withVerbosity(value);
} else if ("packages".equalsIgnoreCase(key)) {
pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
} else if ("name".equalsIgnoreCase(key)) {
setName(value);
} else if ("strict".equalsIgnoreCase(key)) {
strict = Boolean.parseBoolean(value);
} else if ("schema".equalsIgnoreCase(key)) {
schemaResource = value;
} else if ("monitorInterval".equalsIgnoreCase(key)) {
final int intervalSeconds = Integer.parseInt(value);
if (intervalSeconds > 0) {
getWatchManager().setIntervalSeconds(intervalSeconds);
if (configFile != null) {
FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners);
getWatchManager().watchFile(configFile, watcher);
}
}
} else if ("advertiser".equalsIgnoreCase(key)) {
createAdvertiser(value, configSource, buffer, "text/xml");
}
}

AbstractConfiguration

 @Override
public void start() {
// Preserve the prior behavior of initializing during start if not initialized.
if (getState().equals(State.INITIALIZING)) {
initialize();
}
LOGGER.debug("Starting configuration {}", this);
this.setStarting();
if (watchManager.getIntervalSeconds() > 0) {
watchManager.start();
}
if (hasAsyncLoggers()) {
asyncLoggerConfigDisruptor.start();
}
final Set<LoggerConfig> alreadyStarted = new HashSet<>();
for (final LoggerConfig logger : loggerConfigs.values()) {
logger.start();
alreadyStarted.add(logger);
}
for (final Appender appender : appenders.values()) {
appender.start();
}
if (!alreadyStarted.contains(root)) { // LOG4J2-392
root.start(); // LOG4J2-336
}
super.start();
LOGGER.debug("Started configuration {} OK.", this);
}
  private class WatchWorker implements Runnable {

@Override
public void run() {
for (Map.Entry<File, FileMonitor> entry : watchers.entrySet()) {
File file = entry.getKey();
FileMonitor fileMonitor = entry.getValue();
long lastModfied = file.lastModified();
if (lastModfied > fileMonitor.lastModified) {
logger.info("File {} was modified", file.toString());
fileMonitor.lastModified = lastModfied;
fileMonitor.fileWatcher.fileModified(file);
}
}
}
}
 @Override
public void fileModified(File file) {
for (final ConfigurationListener listener : listeners) {
final Thread thread = new Log4jThread(new ReconfigurationWorker(listener, reconfigurable));
thread.setDaemon(true);
thread.start();
}
}
 /**
* Helper class for triggering a reconfiguration in a background thread.
*/

private static class ReconfigurationWorker implements Runnable {

private final ConfigurationListener listener;
private final Reconfigurable reconfigurable;

public ReconfigurationWorker(final ConfigurationListener listener, final Reconfigurable reconfigurable) {
this.listener = listener;
this.reconfigurable = reconfigurable;
}

@Override
public void run() {
listener.onChange(reconfigurable);
}
}
    /**
* Causes a reconfiguration to take place when the underlying configuration file changes.
*
* @param reconfigurable The Configuration that can be reconfigured.
*/

@Override
public synchronized void onChange(final Reconfigurable reconfigurable) {
LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this);
final Configuration newConfig = reconfigurable.reconfigure();
if (newConfig != null) {
setConfiguration(newConfig);
LOGGER.debug("Reconfiguration completed for {} ({})", contextName, this);
} else {
LOGGER.debug("Reconfiguration failed for {} ({})", contextName, this);
}
}

默认的流程是

LogManager-->LogContext-->创建Configuration-->AbstractConfiguration调用start方法
-->WatcherManager监测到文件变化-->FileWatcher modify 回调-->重新加载配置文件

接入Kafka

<Kafka name="kafkaLog" topic="topic_request_log" ignoreExceptions="false">
<PatternLayout pattern="[%-4level]_|_%d{YYYY-MM-dd HH:mm:ss}_|_%m_|_${sys:ip}"/>
<Property name="bootstrap.servers">bigdata001.dns.org:9092,bigdata002.dns.org:9092</Property>
<Property name="max.block.ms">2000</Property>
</Kafka>

<RollingFile name="failoverKafkaLog" fileName="../log/Service/failoverKafka/request.log"
filePattern="../log/Service/failoverKafka/request.%d{yyyy-MM-dd}.log">

<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout>
<Pattern>[%-4level]_|_%d{YYYY-MM-dd HH:mm:ss}_|_%m_|_${sys:ip}%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
</Policies>
</RollingFile>

<Failover name="Failover" primary="kafkaLog" retryIntervalSeconds="600">
<Failovers>
<AppenderRef ref="failoverKafkaLog"/>
</Failovers>
</Failover>

<!--异步-->
<AsyncLogger name="kafkaLogger" level="INFO" additivity="false">
<appender-ref ref="Failover"/>
</AsyncLogger>

注意点⚠️

  • 必须异步

  • 做断路器、failover、kafka carsh的时候、日志写入本地即可

  • log4j2 Failover appender retryIntervalSeconds的默认值是1分钟,是通过异常来切换的,所以可以适量加大间隔,比如上面的10分钟

  • Kafka appender ignoreExceptions 必须设置为false,否则无法触发Failover

  • 这里有个比较大的坑是max.block.ms Property,KafkaClient包里默认值是60000ms,当Kafka宕机时,尝试写Kafka需要1分钟才能返回Exception,之后才会触发Failover,当请求量大时,log4j2 队列很快就会打满,之后写日志就Blocking,严重影响到主服务响应。所以要设置足够短,队列长度足够长。(异步记录的队列可以看上面的日志记录方式)



文中大部分资料从以下链接中整理总结

https://www.cnblogs.com/hafiz/p/6170702.html https://www.cnblogs.com/yulinlewis/p/10177196.htmlhttps://blog.csdn.net/ThinkWon/article/details/101625124

https://www.cnblogs.com/yulinlewis/p/10177196.html

https://www.jianshu.com/nb/36706646

https://www.jianshu.com/p/0c882ced0bf5

https://www.cnblogs.com/lzb1096101803/p/5796849.html

https://logging.apache.org/log4j/2.x/manual/configuration.html

https://www.jianshu.com/p/82469047acbf

https://www.jianshu.com/p/ba1aa0c52942


以上是关于Log4J2 详细介绍的主要内容,如果未能解决你的问题,请参考以下文章

Log4j2完整XML参考(详细注释说明)

log4j2配置文件xml详细了解

log4j的简单介绍

关于SpringBoot 项目中使用Log4j2详细

Log4j2基本使用入门

Nginx配置文件详细介绍