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>
配置
根节点
Appenders 节点
Loggers 节点
根节点
有两个比较重要的属性 status 和 monitorinterval
status 用来指定log4j 本身打印日志的级别
monitorinterval 指定log4j 自动重新检测配置文件是否改变然后重新加载的时间讲过、单位为秒、必须大于0
常见的配置方式有xml,yaml,json。与之对应的类就是XmlConfiguration、JsonConfiguration....
Appenders 节点
常用的节点有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 节点
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 详细介绍的主要内容,如果未能解决你的问题,请参考以下文章