官网地址:https://logging.apache.org/log4j/2.x/、Maven 仓库地址:https://search.maven.org/artifact/org.apache.logging.log4j/log4j-api
Log4j2是对Log4j1x的升级版,同时修复与升级了Logback的不足,被誉为是目前最优秀的Java日志框架。Apache Log4j2日志框架的主要特征:
Log4j2 除了提供日志实现以外,也拥有一套自己的独立的门面。不过目前市面上最主流的日志门面是SLF4J,虽然Log4j2也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将Log4j2看作是日志的实现,SLF4J + Log4j2 的组合是市场上最强大的日志功能实现方式,绝对是主流日志框架。
Log4j2中分为:门面(log4j-api.jar)和实现 (log4j-core.jar) 两个模块。门面与SLF4J是一个类型,属于日志抽象门面。而实现部分才是Log4j2的核心:
Log4j2 重要类的概念:
1、使用Log4j做日志门面
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>2.17.2version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.17.2version>
dependency>
package com.xyz;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2Test {
public static void main(String[] args) {
// 定义日志记录器对象,如下没有使用SLF4J门面技术,完全使用的log4j中的类和方法
Logger logger = LogManager.getLogger(Log4j2Test.class);
// log4j2中存在6中日志输出级别
logger.fatal("fatal");
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug");
logger.trace("trace");
}
}
12:10:00.727 [main] FATAL com.xyz.Log4j2Test - fatal
12:10:00.727 [main] ERROR com.xyz.Log4j2Test - error
此时可以发现只输出了error及以上级别的日志,因为Log4j2门面默认是error级别,就说明此时这个日志是使用Log4j2框架实现的。
2、使用SLF4J作为日志的门面
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.36version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>2.17.2version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>2.17.2version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.17.2version>
dependency>
package com.xyz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4j2Test {
public static void main(String[] args) {
// 注意:此时使用的slf4j的门面技术,没有适应到log4j2,输出的是 slf4j的日志级别(有5个级别)
Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
// slf4j中存在5种日志输出级别,此时使用是slf4j的记录器,而不是log4j2的,所以只能输出slf4j中的五种级别
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
}
23:11:22.977 [main] ERROR com.xyz.Log4j2Test - error信息
执行结果看到不管是Log4j2自己的门面还是SLF4J门面搭配Log4j2实现默认日志级别都是是error。
Log4j2在目前JAVA中的日志框架里,异步日志的性能是最高的,没有之一。先来看一下,几种日志框架benchmark对比结果(官方测试结果)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRGuMCwG-1650171303296)(https://logging.apache.org/log4j/2.x/images/async-throughput-comparison.png)]
从图上可以看出,Log4j2的异步(全异步,非混合模式)下的性能,远超Log4j1和Logback,简直吊打。压力越大的情况下,吞吐上的差距就越大。在64线程测试下,Log4j2的吞吐达到了180w+/s,Llogback/Log4j1只有不到20w,相差近十倍。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AjltEjL4-1650171303297)(https://logging.apache.org/log4j/2.x/images/SyncThroughputLoggerComparisonLinux.png)]
上图是同步测试报告,明显可以看出,与各个日志框架对比而言,无论在同步或者异步情况下,log4j2表现更加优异,而其他日志就显得差强人意了。
从Log4j 2.6版本开始(2016年),Log4j2默认就以零GC模式运行了。什么叫零GC呢?就是不会由于Log4j2而导致GC。Log4j2中各种Message对象,字符串数组,字节数组等全部复用,不重复创建,大大减少了无用对象的创建,从而做到“零GC”。
Log4j2还提供了一个MemoryMappedFileAppender,I/O 部分使用MemoryMappedFile来实现,可以得到极高的I/O性能。不过在使用MemoryMappedFileAppender之前,得确定你足够了解MemoryMappedFile的相关知识,否则不要轻易使用呦。
Log4j的API与SLF4J相比,提供了更丰富的参数格式化功能。使用 {} 占位符格式化参数。在SLF4J里,我们可以用 {} 的方式来实现“format”的功能(参数会直接toString替换占位符),像下面这样:
org.apache.logging.log4j.Logger logger = LogManager.getLogger("com.xyz");
logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthdayCalendar());
Log4j2 中除了支持 {} 的参数占位符,还支持 String.format 的形式
org.apache.logging.log4j.Logger logger = LogManager.getFormatterLogger("com.xyz");
logger.debug("Logging in user %s with birthday %s", user.getName(), user.getBirthdayCalendar());
logger.debug("Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());
logger.debug("Integer.MAX_VALUE = %,d、Long.MAX_VALUE = %,d", Integer.MAX_VALUE, Long.MAX_VALUE);
注意:如果想使用 String.format 的形式,需要使用 LogManager.getFormatterLogger 而不是 LogManager.getLogger
Log4j2的Logger接口中,还有一个logger.printf()方法,无需创建 LogManager.getFormatterLogger,就可以使用 String.format 的形式
org.apache.logging.log4j.Logger logger = LogManager.getLogger("com.xyz");
logger.printf(Level.INFO, "Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());
logger.debug("Opening connection to {}...", someDataSource);
这个功能虽然小,但非常实用。在某些业务流程里,为了留根或追溯问题,需要完整的打印入参,一般是把入参给用JSON序列化后用debug级别打印:
org.apache.logging.log4j.Logger logger = LogManager.getLogger("com.xyz");
logger.debug("入参报文:{}", JSON.toJSONString(policyDTO));
如果需要追溯问题时,会将系统的日志级别调到debug,这样就可以打印。但是这里有个问题,虽然在info级别下debug不会输出内容,但JSON.toJSONString()这个序列化的代码一定会执行,严重影响正常流程下的执行效率。
我们期望的结果是info级别下,连序列化都不执行。这里可以通过logger.isDebugEnable()来判断当前配置下debug级别是否可以输出:
org.apache.logging.log4j.Logger logger = LogManager.getLogger("com.xyz");
if(logger.isDebugEnabled()) {
logger.debug("入参报文:{}", JSON.toJSONString(policyDTO));
}
这样虽然可以避免不必要的序列化,但每个地方都这么写还是有点难受的,一行变成了三行。
Log4j2的Logger对象,提供了一系列Lambda的支持,通过这些接口可以实现“惰性”打日志:
void fatal(String message, Supplier... paramSuppliers);
void error(String message, Supplier<?>... paramSuppliers);
void warn(String message, Supplier... paramSuppliers);
void info(String message, Supplier<?>... paramSuppliers);
void debug(String message, Supplier<?>... paramSuppliers);
void trace(String message, Supplier<?>... paramSuppliers);
org.apache.logging.log4j.Logger logger = LogManager.getLogger("com.xyz");
// 等同于下面的先判断,后打印
logger.debug("入参报文:{}",() -> JSON.toJSONString(policyDTO));
// 与上面效果一样
if(logger.isDebugEnabled()) {
logger.debug("入参报文:{}",JSON.toJSONString(policyDTO));
}
这种 Supplier + Lambda 的形式,等同于上面的先判断 logger.isDebugEnable() 然后打印,三行的代码变成了一行。
Log4j2同时支持XML/JSON/YML/Properties四种形式的配置文件,不过最主流的还是XML的方式,最直观。来查看logback和log4j2的配置文件对比:
logback.xml
<configuration>
<appender name = "File" class= "ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/archives/app-%d{yyyy-MM-dd}.log.gzfileNamePattern>
<maxFileSize>1 GBmaxFileSize>
rollingPolicy>
appender>
<root level="info">
<appender-ref ref="File"/>
root>
configuration>
log4j2.xml
<Configuration status="warn" name="XInclude" xmlns:xi="http://www.w3.org/2001/XInclude">
<Appenders>
<RollingFile name="File" fileName="logs/app.log" filePattern="logs/archives/app-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p [%t] %-40.40c{1.} : %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="1 GB"/>
Policies>
RollingFile>
Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="File"/>
Root>
Loggers>
Configuration>
在log4j2中,appender的配置从使用 Appender 实现名即标签名的形式,语法上更简洁一些:
<RollingFile name="File">RollingFile>
<appender name = "File" class= "ch.qos.logback.core.rolling.RollingFileAppender">appender>
Log4j2由如下几部分构成:
DefaultConfiguration类中提供的默认配置将设置,通过debug可以在LoggerContext类中发现
package org.apache.logging.log4j.core;
public class LoggerContext extends AbstractLifeCycle
implements org.apache.logging.log4j.spi.LoggerContext, AutoCloseable, Terminable, ConfigurationListener, LoggerContextShutdownEnabled {
/**
* The Configuration is volatile to guarantee that initialization of the Configuration has completed before the
* reference is updated.
*/
private volatile Configuration configuration = new DefaultConfiguration();
}
启动时可以通过debug看到:
%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
实际上也能从默认配置类中看到一些默认的配置:
public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration {
protected void setToDefault() {
// LOG4J2-1176 facilitate memory leak investigation
setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode()));
final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
.withPattern(DefaultConfiguration.DEFAULT_PATTERN)
.withConfiguration(this)
.build();
final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
appender.start();
addAppender(appender);
final LoggerConfig rootLoggerConfig = getRootLogger();
rootLoggerConfig.addAppender(appender, null, null);
rootLoggerConfig.setLevel(getDefaultLevel());
}
}
1、Log4j2默认在classpath下查找配置文件,可以修改配置文件的位置。在非web项目中:
ConfigurationSource source = new ConfigurationSource(new FileInputStream("D:/log4j2.xml"));
Configurator.initialize(null, source);
org.apache.logging.log4j.Logger logger = LogManager.getLogger(Log4j2Test.class);
2、如果是在SpringBoot项目,在appliaction.properties 或 application.yml 中配置即可:
logging.config=classpath:log4j2.xml
3、如果是web项目,在web.xml中添加(Listener使用的是Log4j2默认的):
<listener>
<listener-class>org.apache.logging.log4j.web.Log4jServletContextListenerlistener-class>
listener>
<context-param>
<param-name>log4jConfigurationparam-name>
<param-value>/WEB-INF/conf/log4j2.xmlparam-value>
context-param>
4、如果是web项目,在web.xml中添加(使用自定义Listener):
<listener>
<listener-class>com.xyz.Log4j2ConfigListenerlistener-class>
listener>
<context-param>
<description>Logging Configuration File Pathdescription>
<param-name>log4j.configurationFileparam-name>
<param-value>log4j/log4j2.xmlparam-value>
context-param>
package com.xyz;
import java.util.Enumeration;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.logging.log4j.core.config.Configurator;
public class Log4j2ConfigListener implements ServletContextListener {
private static final String KEY = "log4j.configurationFile";
@Override
public void contextDestroyed(ServletContextEvent arg0) {
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
String fileName = getContextParam(arg0);
Configurator.initialize("Log4j2", "classpath:" + fileName);
}
@SuppressWarnings("unchecked")
private String getContextParam(ServletContextEvent event) {
Enumeration<String> names = event.getServletContext().getInitParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
String value = event.getServletContext().getInitParameter(name);
if(name.trim().equals(KEY)) {
return value;
}
}
return null;
}
}
模板 1(简约版):
<Configuration monitorInterval="1" status="ERROR" strict="true" name="LogConfig">
<Properties>
<Property name="logbasedir">E:/logsProperty>
<Property name="log.layout">%d %-5p %t (%c:%L) - %m%nProperty>
Properties>
<Appenders>
<Appender type="Console" name="STDOUT">
<Target>SYSTEM_OUTTarget>
<Layout type="PatternLayout" pattern="${log.layout}"/>
Appender>
<Appender type="RollingFile" name="FILE"
fileName="${logbasedir}/jutap-${sys:APPNAME}.log"
filePattern = "${logbasedir}/jutap-${sys:APPNAME}-%d{yyyy-MM-dd}.%i.log">
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="100 MB"/>
Policies>
<Layout type="PatternLayout">
<Charset>GBKCharset>
<Pattern>${log.layout}Pattern>
Layout>
Appender>
<Appender type="RollingFile"
name="ExceptionLog"
fileName="${logbasedir}/exception-${sys:APPNAME}.log"
filePattern="${logbasedir}/exception-${sys:APPNAME}-%d{yyyy-MM-dd}.%i.log">
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="100 MB"/>
Policies>
<Layout type="PatternLayout">
<Charset>GBKCharset>
<Pattern>${log.layout}Pattern>
Layout>
Appender>
Appenders>
<Loggers>
<Logger name="exception" level="error" additivity="false">
<AppenderRef ref="ExceptionLog"/>
Logger>
<Root level="info">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="FILE"/>
Root>
<Logger name="com.garfield.learn" level="debug"/>
<Logger name="com.garfield.learnp" level="info"/>
Loggers>
Configuration>
模板 2(复杂版):
<Configuration status="WARN" monitorInterval="600">
<Properties>
<Property name="LOG_HOME">${sys:catalina.home}/WebAppLogs/SSHExampleProperty>
Properties>
<Appenders>
<Console name="console_out_appender" target="SYSTEM_OUT">
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="ACCEPT"/>
<PatternLayout pattern="%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n"/>
Console>
<Console name="console_err_appender" target="SYSTEM_ERR">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n"/>
Console>
<RollingRandomAccessFile name="trace_appender"
immediateFlush="true"
fileName="${LOG_HOME}/trace.log"
filePattern="${LOG_HOME}/trace/trace - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout>
<pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%npattern>
PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="2MB"/>
Policies>
<Filters>
<ThresholdFilter level="debug" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
Filters>
RollingRandomAccessFile>
<RollingRandomAccessFile name="debug_appender"
immediateFlush="true"
fileName="${LOG_HOME}/debug.log"
filePattern="${LOG_HOME}/debug/debug - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout>
<pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%npattern>
PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="2MB"/>
Policies>
<Filters>
<ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
Filters>
RollingRandomAccessFile>
<RollingRandomAccessFile name="info_appender"
immediateFlush="true"
fileName="${LOG_HOME}/info.log"
filePattern="${LOG_HOME}/info/info - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout>
<pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%npattern>
PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="2MB"/>
Policies>
<Filters>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
Filters>
RollingRandomAccessFile>
<RollingRandomAccessFile name="warn_appender"
immediateFlush="true"
fileName="${LOG_HOME}/warn.log"
filePattern="${LOG_HOME}/warn/warn - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout>
<pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%npattern>
PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="2MB"/>
Policies>
<Filters>
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
Filters>
RollingRandomAccessFile>
<RollingRandomAccessFile name="error_appender"
immediateFlush="true"
fileName="${LOG_HOME}/error.log"
filePattern="${LOG_HOME}/error/error - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout>
<pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%npattern>
PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="2MB"/>
Policies>
<Filters>
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
Filters>
RollingRandomAccessFile>
Appenders>
<Loggers>
<root level="trace">
<appender-ref ref="console_out_appender"/>
<appender-ref ref="console_err_appender"/>
<appender-ref ref="trace_appender"/>
<appender-ref ref="debug_appender"/>
<appender-ref ref="info_appender"/>
<appender-ref ref="warn_appender"/>
<appender-ref ref="error_appender"/>
root>
<logger name="org.springframework.core" level="info"/>
<logger name="org.springframework.beans" level="info"/>
<logger name="org.springframework.context" level="info"/>
<logger name="org.springframework.web" level="info"/>
<logger name="org.jboss.netty" level="warn"/>
<logger name="org.apache.http" level="warn"/>
Loggers>
Configuration>
package com.xyz;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
public class Log4j2Test {
private static final Logger logger = LogManager.getLogger(Log4j2Test.class);
public static void main(String[] args) {
logger.fatal("fatal...");
logger.error("error...");
// LoggerContext getContext(final boolean currentContext):获取log4j日志上下文,false表示返回合适调用方的上下文
LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
// 返回当前配置,发生重新配置时,将替换该配置。
Configuration configuration = loggerContext.getConfiguration();
// 查找记录器名称的相应 LoggerConfig
LoggerConfig loggerConfig = configuration.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
// 设置日志级别,如果 level 值不属于 ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF,则默认会设置为 DEBUG
loggerConfig.setLevel(Level.toLevel("FATAL"));
// 根据当前配置更新所有记录器
loggerContext.updateLoggers();
// 查询 root(根)日志输出级别结果返回
System.out.println("设置后的Root日志级别: " + LogManager.getRootLogger().getLevel().name());
logger.fatal("fatal...");
logger.error("error...");
}
}
15:27:25.768 [main] FATAL com.xyz.Log4j2Test - fatal...
15:27:25.768 [main] ERROR com.xyz.Log4j2Test - error...
设置后的Root日志级别: FATAL
15:27:25.778 [main] FATAL com.xyz.Log4j2Test - fatal...
Log4j2配置文件通常为:log4j2.xml和log4j2-test.xml,加载顺序为:log4j2-test.xml » log4j2.xml。
XML配置文件语法
;
<Configuration>
<Properties>
<Property name="name1">valueproperty>
<Property name="name2" value="value2"/>
Properties>
<filter ... />
<Appenders>
<appender ... >
<filter ... />
appender>
...
Appenders>
<Loggers>
<Logger name="name1" level="level">
<filter ... />
Logger>
...
<Root level="level">
<AppenderRef ref="name"/>
Root>
Loggers>
Configuration>
节点名称 | 含义 | 配置项 |
---|---|---|
Configuration | 配置的根节点,有两个子节点:Appenders和Loggers(表明可以定义多个Appender和Logger) | 该节点有两个配置项status:用于指定log4j2本身的日志打印级别,日志级别从低到高分为TRACE、DEBUG、INFO、WARN、ERROR、FATAL,如果设置为WARN,则低于WARN的信息都不会输出。monitorinterval:用于指定log4j自动重新配置的监测间隔时间,单位是秒(s),最小是5s |
Appenders | 负责将日志输出到目的地(这个目的地可以是控制台,文件,数据库,甚至是邮件),定义输出内容,输出格式,输出方式,日志保存策略等 | log4j2支持的appender种类非常多,完整的appender可通过官方网站进行查询。其中,比较常见的appender有:ConsoleAppender、FileAppender、RandomAccessFileAppender、RollingFileAppender、RollingRandomAccessFileAppender,另外,还提供异步的AsyncAppender,实现异步处理日志事件 |
Loggers | LogEvent生产的源头,只有定义了logger并引入的appender,appender才会生效 | 常见的有两种:Root和Logger |
根节点Configuration中有两个常用的属性:status、monitorterval、name。示例及解释如下:
Configuration元素的属性:
<Configuration status="WARN" monitorInterval="30" name="xyzConfiguration">
Configuration>
子节点元素Properties是用来定义常量,以便在其他配置项中引用,该配置是可选的,例如定义日志的存放位置
<Properties>
<Property name="nowDate">%date{yyyy-MM-dd}Property>
<Property name="logDir">${sys:catalina.home}/logs/Property>
<Property name="logPath">./logsProperty>
<property name="pattern_format">[%d{HH:mm:ss:SSS}] [%-5p] - %l -- %m%nproperty>
Properties>
Prefix | Context |
---|---|
base64 | Base64编码数据,格式为 b a s e 64 : B a s e 6 4 e n c o d e d d a t a , 如 : {base64:Base64_encoded_data},如: base64:Base64encodeddata,如:{base64:SGVsbG8gV29ybGQhCg==} 输出 Hello World! |
bundle | 资源绑定。格式为: b u n d l e : B u n d l e N a m e : B u n d l e K e y , 示 例 如 : {bundle:BundleName:BundleKey},示例如: bundle:BundleName:BundleKey,示例如:{bundle:com.domain.Messages:MyKey} |
ctx | 线程上下文映射(MDC),如:$${ctx:loginId} |
date | 插入指定的时间格式。如:$${date:MM-dd-yyyy} |
env | 系统环境变量,格式为${env:ENV_NAME} 或者 e n v : E N V N A M E : − d e f a u l t v a l u e , 如 : {env:ENV_NAME:-default_value} ,如: env:ENVNAME:−defaultvalue,如: e n v : U S E R , {env:USER}, env:USER,${env:USER:-jdoe} |
jndi | 设置JNDI上下文,如:$${jndi:logging/context-name}。注意:在Android下面不能使用 |
sys | 系统属性,格式为${sys:some.property}或者 s y s : s o m e . p r o p e r t y : − d e f a u l t v a l u e , 如 : {sys:some.property:-default_value},如: sys:some.property:−defaultvalue,如:{sys:logPath} |
jvmrunargs | 通过 JMX 访问的 JVM 输入参数,但不是主参数; 请参阅 RuntimeMXBean.getInputArguments()。 在Android上不可用 |
log4j | Log4j配置属性。 表达式 ${log4j:configLocation} 和 ${log4j:configParentLocation} 分别提供了log4j配置文件及其父文件夹的绝对路径 |
main | 使用 MapLookup.setMainArguments(String[]) 设置的值 |
map | 来自 MapMessage 的值 |
Loggers 定义日志输出位置,包含Logger和Root结点。Root用来指定项目的根日志,若没有单独指定Logger,则会默认使用该Root日志输出
Root:每个配置都必须有一个根记录器Root。如果未配置,则将使用默认根LoggerConfig,其级别为ERROR且附加了ConsoleAppender。根记录器和其他记录器之间的主要区别是:1.根记录器只有level属性。2.根记录器没有name属性。3.根记录器不支持additivity属性,因为它没有父级
Logger:用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。Logger元素必须有一个name属性,每个Logger可以使用TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一配置级别。如果未指定级别,则默认为ERROR。可以为additivity属性分配值true或false。如果省略该属性,则将使用默认值true。子节点AppenderRef用于指定日志输出到哪个Appender,若没有指定,默认集成自Root
AsyncLogger:异步日志,Log4j2有三种模式:全异步日志,混合模式,同步日志,性能从高到底,线程越多效率越高,也可以避免日志卡死线程情况发生
<Configuration>
<Loggers>
<Logger name="org.springframework" level="INFO">logger>
<Logger name="org.mybatis" level="INFO">logger>
<Logger name="com.xyz" level="false" additivity="false">
<AppenderRef ref="consoleAppender" />
Logger>
<Root level="info">
<AppenderRef ref="consoleAppender" />
<AppenderRef ref="fileAppender" />
Root>
<AsyncLogger name="AsyncLogger" level="trace" includeLocation="true" additivity="false">
<appender-ref ref="RollingFileError"/>
AsyncLogger>
Loggers>
Configuration>
可以使用这个标签来引入外部xml文件
<configuration status="warn" name="XIncludeDemo">
<xi:include href="log4j-xinclude-appenders.xml" />
<xi:include href="log4j-xinclude-loggers.xml" />
configuration>
参考官网:http://logging.apache.org/log4j/2.x/manual/appenders.html
Appenders是输出源,用于定义日志输出的地方。Log4j2支持的输出源有很多:ConsoleAppender、FileAppender、RandomAccessFileAppender、RollingFileAppender、RollingRandomAccessFileAppender、AsyncAppender 等,如下是 Appender 所有分类(参考官网):
名称 | 描述 |
---|---|
AsyncAppender | 使用一个单独的线程记录日志,实现异步处理日志时间 |
CassandraAppender | 将日志信息输出到一个Apache的Cassandre数据库 |
ConsoleAppender | 将日志信息输出到控制台 |
FailoverAppender | 包含其他Appenders,按照顺序尝试,直至成功或结尾 |
FileAppender | 一个OutputStreamAppenders,将日志输出到文件 |
FlumeAppender | 将日志输出到Apache Flume系统 |
JDBCAppender | 将日志通过JDBC输出到关系型数据库 |
JMSAppender | 将日志输出到JMS(Java Message Service) |
JPAAppender | 将日志输出到JPA框架 |
HTTPAppender | 通过HTTP输出到日志 |
KafkaAppender | 将日志输出到Apache Kafka |
MemoryMappedFileAppender | 将日志输出到一块文件关联的内存 |
NoSQLAppender | 将日志输出到NoSQL数据库,如MongoDB、CouchDB |
OutputStreamAppender | 将日志输出到一个OutputStream |
RandomAccessFileAppender | 性能比FileAppender高20%~200%的文件输出Appender |
RewriteAppender | 允许对日志信息进行加工掩码输出 |
RollingFileAppender | 按日志文件最大长度限度生成新文件 |
RollingRandomAccessFileAppender | 添加了缓存的RollingFileAppender |
RoutingAppender | 将日志事件分类,允许通过规则路由日志到不同的输出地(子Appender) |
SMTPAppender | 将日志输出到邮件 |
ScriptAppenderSelectorAppender | 使用自定义脚本的形式来输出日志 |
SocketAppender | 将日志输出到Socket |
SyslogAppender | 是一个SocketAppender,将日志输出到远程系统日志 |
ZeroMQ/JeroMQAppender | 使用JeroMQ库将日志输出到ZeroMQ终端 |
<Console name="consoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%date %logger %processId %threadId %method %class %file %highlight{%level} : %green{%msg} %n" />
Console>
<File name="fileAppender" fileName="log4j2Study_${date:yyyy-MM}.log">
<PatternLayout pattern="%date %logger %level : %msg%n" />
File>
<RandomAccessFile name="randomFileAppender" fileName="log4j2_randomFileAppender.log">
<PatternLayout pattern="%date %logger %level : %msg%n" />
RandomAccessFile >
<RollingFile name="rollingFileAppender" fileName="log4j2_rollingFileAppender.log"
filePattern="$${date:yyyy-MM}/%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%date %logger %level : %msg%n" />
<Policies>
<CronTriggeringPolicy schedule="0 0 * * * ?"/>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="100 MB" />
Policies>
<DefaultRolloverStrategy max="7" />
<DefaultRolloverStrategy>
<Delete>
<IfFileName glob="*.log.gz" />
<IfLastModified age="60d" />
Delete>
DefaultRolloverStrategy>
RollingFile>
<RollingRandomAccessFile>RollingRandomAccessFile>
<Async name="fileAsync" >
<AppenderRef ref="fileAppender" />
Async>
<Cassandra>Cassandra>
<JDBC>JDBC>
<JMS>JMS>
<Kafka>Kafka>
<NoSql>NoSql>
<SMTP>SMTP>
<Socket>Socket>
<Http>Http>
<Rewrite>Rewrite>
ConsoleAppender:使用该Appender可以将日志信息输出到控制台,参数与配置如下
Console 元素的属性:
Console 元素的节点:
<Configuration status="warn" monitorinterval="10" name="myApp">
<Appenders>
<Console name="consoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%m%n"/>
Console>
Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="consoleAppender"/>
Root>
Loggers>
Configuration>
FileAppender:用于将LogEvent写入到一个文件中,是文件输出源,用于将日志写入到指定的文件,其底层是一个OutputStreamAppender
File 元素的属性
File 元素的节点
<Configuration status="warn" monitorinterval="10" name="myApp">
<Appenders>
<File name="fileAppender" fileName="logs/app.log" append="true">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%nPattern>
PatternLayout>
File>
Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="fileAppender"/>
Root>
Loggers>
Configuration>
RandomAccessFile参考:https://www.cnblogs.com/jyy599/p/12076662.html
与FileAppender很相似,除了将BufferdOutputStream替换为了ByteBuffer + RandomAccessFile,还使用了缓存,它的性能比FileAppender高20%~200%
RandomAccessFile 元素的属性
RandomAccessFile 元素的节点
<Configuration status="warn" monitorinterval="10" name="myApp">
<Appenders>
<RandomAccessFile name="randomAccessFile" fileName="logs/app.log">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%nPattern>
PatternLayout>
RandomAccessFile>
Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="randomAccessFile"/>
Root>
Loggers>
Configuration>
RollingFile用于实现日志文件动态更新的Appender,当满足条件(日志大小、指定时间等)重命名或打包原日志文件进行归档生成新文件,比File更强大
RollingFileAppender是一个OutputStreamAppender,先写入指定文件,并根据TriggeringPolicy和RolloverPolicy滚动文件。RollingFile需要两个策略的支持,分别是TriggeringPolicy和RolloverPolicy。TriggeringPolicy定义何时应该生成新的日志文件而RolloverPolicy则决定如何生成新的日志文件。RollingFile不支持文件锁
RollingFile 元素的属性:
RollingFile 元素的节点:
如下配置:有三个Trigger,表示每秒生成一个文件或者每小时生成一个文件或者当文件大小达到250MB时生成一个新的文件;有两个Strategy,表示最多保持文件数量为10,达到最大值后,旧的文件将被删除,或者文件名符合.log.gz后缀 or 超过60天 也会删除旧文件。
<Configuration status="warn" monitorinterval="0" name="myApp">
<Appenders>
<RollingFile name="rollingFile"
fileName="logs/app.log"
filePattern="logs/app-%d{yyyy-MM-dd_HH-mm-ss}-%i.log.zip">
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy />
<CronTriggeringPolicy schedule="0 0 * * * ?"/>
<SizeBasedTriggeringPolicy size="250 MB"/>
Policies>
<DefaultRolloverStrategy max="10"/>
<DefaultRolloverStrategy>
<Delete basePath="logs/" maxDepth="2">
<IfFileName glob="logs/*.log.zip"/>
<IfLastModified age="60d"/>
Delete>
DefaultRolloverStrategy>
RollingFile>
Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="rollingFile"/>
Root>
Loggers>
Configuration>
如下单独使用三个示例进行详细讲解:
(1)基于大小的滚动策略
日志先写入logs/app.log中,每当文件大小达到100MB时或经过1天,按照在logs/2020-09/目录下以app-2020-09-09-1.log.gz格式对该日志进行压缩重命名并归档,并生成新的文件app.log进行日志写入。其中,filePattern属性的文件格式中%i
就类似于一个整数计数器,受到
控制,要特别注意的是:当文件个数达到10个的时候会循环覆盖前面已归档的1-10个文件。若不设置该参数,默认为7。
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%nPattern>
PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="100 MB"/>
Policies>
<DefaultRolloverStrategy max="10"/>
RollingFile>
Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
Root>
Loggers>
Configuration>
(2)基于时间间隔的滚动策略
日志先写入logs/app.log中,每当文件的时间间隔到达6小时(由%d{yyyy-MM-dd-HH}
决定,也可以设置成%d{yyyy-MM-dd-HH-mm}
,则间隔为分钟级别),触发rollover操作。如下配置设置好后,10点的日志开始重启服务,则从11点触发一次rollover操作,生成2022-04-10-10.log.gz
对该日志进行压缩重命名并归档,并生成新的文件app.log进行日志写入;然后,每间隔6小时,则下一次是17点触发一次,生成2022-04-10-17.log.gz对该日志进行压缩重命名并归档,并生成新的文件app.log进行日志写入。
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%nPattern>
PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
Policies>
RollingFile>
Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
Root>
Loggers>
Configuration>
(3)基于时间间隔和文件大小的滚动策略
日志先写入logs/app.log中,每当文件大小达到100MB或者当时间间隔到达6小时(由%d{yyyy-MM-dd-HH}
决定),触发rollover操作,按照在logs/2022-04/
目录下以app-2022-04-1-1.log.gz
格式对该日志进行压缩重命名并归档,并生成新的文件app.log进行日志写入。
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile"
fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%nPattern>
PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
Policies>
RollingFile>
Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
Root>
Loggers>
Configuration>
RollingRandomAccessFile类似于标准的RollingFile,除了它总是被缓冲(且不能被关闭),并且在内部它使用 ByteBuffer + RandomAccessFile 而不是BufferedOutputStream。与RollingFileAppender相比,当 bufferedIO = true 时,性能提升 20-200%。
RollingFile 元素的属性:
RollingFile 元素的节点:
<RollingRandomAccessFile>RollingRandomAccessFile>
异步输出日志,默认使用 java.util.concurrent.ArrayBlockingQueue 来实现的,不需要其他的外部库,这个配置在多线程的情况下需要小心,可能会出现锁竞争,如果在多线程下,可以使用Disruptor库(一个无锁的线程间通信库,而不是队列,从而产生更高的吞吐量和更低的延迟),可以使用asyncRoot或者asyncLogger来进行配置。
<Async name="Async">
<AppenderRef ref="RollingFileAppender" />
<AppenderRef ref="Console" />
<LinkedTransferQueue/>
Async>
官网地址:https://logging.apache.org/log4j/2.x/manual/filters.html
Filter用于过滤日志(是可选的)Log4j2会在日志产生时自动调用预先配置的Filter的filter方法进行过滤,以便获得是否允许打印的标识。
是否允许打印的标识是一个 Result 类型的枚举,他的值有三种:ACCEPT、DENY、NEUTRAL
重点讲解 NEUTRAL,如果只有一个 Filter,那么 NEUTRAL 与 ACCEPT 没有任何区别,只有在多个 Filter 级联使用时,NEUTRAL 才有意义,他表示由下一个 filter 决定是否 ACCEPT。
通常 filter 并不直接决定最终的结果,因为不同的场景下,filter 命中后的行为并不一定相同,因此 filter 只返回命中或未命中,然后由业务具体需要决定是否允许打印相应的日志是更好的选择。Log4j2 的 Filter 就是基于上述原则创建的,他提供了 onMatch(命中)与 onMisMatch(未命中)两个参数供用户配置,filter 值返回当前场景命中或未命中
Log4j2 允许你将 Filter 配置为全局有效或对某个 Appender 生效。
常用的过滤器如下几类,解析请查看注释
<BurstFilter level="info" rate="16" maxBurst="100" onMatch="NEUTRAL" onMismatch="DENY" />
<DynamicThresholdFilter key="tcKey" defaultThreshold="info" onMatch="ACCEPT" onMismatch="DENY" >
<KeyValuePair key="tcVal1" value="info" />
<KeyValuePair key="tcVal2" value="error" />
DynamicThresholdFilter>
<MapFilter onMatch="ACCEPT" onMismatch="DENY" >
<KeyValuePair key="mmKey1" value="mmVal1" />
<KeyValuePair key="mmKey2" value="mmVal2" />
MapFilter>
<MarkerFilter marker="mk1" onMatch="ACCEPT" onMismatch="DENY" />
<RegexFilter regex=".*or.*" onMatch="ACCEPT" onMismatch="DENY" />
<ContextMapFilter onMatch="ACCEPT" onMismatch="DENY" operator="or">
<KeyValuePair key="tcKey" value="tcVal1" />
ContextMapFilter>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
<TimeFilter start="00:00:00" end="01:00:00" onMatch="ACCEPT" onMismatch="DENY" />
<Filters>Filters>
BurstFilter 可以控制每秒日志量,对于超过数量的日志进行丢弃。实际就是:控制日志打印速度
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
<BurstFilter level="INFO" rate="16" maxBurst="100"/>
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
<TimeBasedTriggeringPolicy />
RollingFile>
TimeFilter 是限制时间的Filter,允许只在一天中的指定时间进行日志记录:
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
<TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
<TimeBasedTriggeringPolicy />
RollingFile>
MarkerFilter:过滤不同类型的日志。有的时候,我们希望根据日志中的标记来决定不同的日志输出到不同的位置。MarkerFilter 实现了这一功能:
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
<MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
<TimeBasedTriggeringPolicy />
RollingFile>
// MarkerFilter中使用
org.apache.logging.log4j.Logger log4j = org.apache.logging.log4j.LogManager.getLogger(Log4j2Test.class);
Marker mk = MarkerManager.getMarker("mk1");
logger.info(mk,"info log by markerFilter");
MapFilter允许对MapMessage中的数据元素进行过滤
<MapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or">
<KeyValuePair key="eventId" value="Login"/>
<KeyValuePair key="eventId" value="Logout"/>
MapFilter>
<Root level="error">
<MapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or">
<KeyValuePair key="eventId" value="Login"/>
<KeyValuePair key="eventId" value="Logout"/>
MapFilter>
<AppenderRef ref="RollingFile">
AppenderRef>
Root>
<Root level="error">
<AppenderRef ref="RollingFile">
<MapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or">
<KeyValuePair key="eventId" value="Login"/>
<KeyValuePair key="eventId" value="Logout"/>
MapFilter>
AppenderRef>
Root>
// MapFilter中使用
org.apache.logging.log4j.Logger log4j = org.apache.logging.log4j.LogManager.getLogger(Log4j2Test.class);
MapMessage mm = new MapMessage();
mm.put("mmKey1","mmVal1");
mm.put("val","this info msg");
logger.info(mm);
DynamicThresholdFilter:动态日志级别设置。很多时候,我们需要借助更多的日志来进行问题排查,但过多的日志又势必会对线上服务的性能以及磁盘等资源造成压力,此时有一个好的选择,那就是打印丰富的 debug 级别的日志,而 logger 的 level 至少定义在 info 级别以上,这样实际上在生产环境中,这些 debug 级别的日志并不会被打印出来,而在测试环境中,只需要改变 logger 的 level 就可以打开这些 debug 日志的打印,方便我们排查问题。
有时我们更想要知道线上场景下究竟发生了什么,但现实情况我们又不能让所有人都打印出 debug 级别的日志,有什么办法只让符合条件的请求打印出 debug 级别的日志吗?log4j2 用 DynamicThresholdFilter 解决了这一问题。
<DynamicThresholdFilter key="loginId" defaultThreshold="ERROR" onMatch="ACCEPT" onMismatch="NEUTRAL">
<KeyValuePair key="user1" value="DEBUG"/>
<KeyValuePair key="user2" value="INFO"/>
DynamicThresholdFilter>
// 定义ThreadContext值,可用于在生产环境中,对指定用户或场景进行跟踪
org.apache.logging.log4j.Logger log4j = org.apache.logging.log4j.LogManager.getLogger(Log4j2Test.class);
ThreadContext.put("loginId", "user1");
logger.debug("{} log", "info1");
ThreadContext.put("loginId", "user2");
logger.info("{} log", "info2");
这个配置表示:默认日志级别为 ERROR 级别,但符合ThreadContext.get("loginId")
或 MDC.get("loginId")
为 user1 的请求日志级别为 DEBUG。这样,我们只需要在日志打印前执行ThreadContext.put("loginId", "user1")
或MDC.put("loginId", "user1")
就可以实现动态改变本次请求的日志级别了,这对于线上 vip 用户问题的排查是十分方便的
CompositeFilter 复合过滤器提供了一种指定多个过滤器的方法。他以Filters元素加入到配置中,元素里面可以配置多个过滤器。该元素不支持添加参数
<Filters>
<Marker marker="EVENT" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<DynamicThresholdFilter key="loginId" defaultThreshold="ERROR" onMatch="ACCEPT" onMismatch="NEUTRAL">
<KeyValuePair key="user" value="DEBUG"/>
DynamicThresholdFilter>
Filters>
msg.contains(this.text) ? onMatch : onMismatch;
官网地址:http://logging.apache.org/log4j/2.x/manual/layouts.html
PatternLayout:用于指定输出日志的格式,控制台或文件输出源(Console、File、RollingRandomAccessFile)都必须包含一个PatternLayout节点,用于指定输出的格式(如 日志输出的时间 文件 方法 行数 等格式),例如 pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5level %logger{0} - %msg%n"
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5level %logger{0} - %msg%n" charset="UTF-8"/>
<PatternLayout charset="UTF-8">
<Pattern>%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5level %logger{0} - %msg%nPattern>
PatternLayout>
具体详情可以参考官网或本人的Logback中的记录:
Conversation Word | Effect |
---|---|
变量名 | |
%logger、%c | 当前日志名称,如: Slf4jAndLogbackMain |
%class、%C | 日志调用所在类,如: com.dragon.study.log.Slf4jAndLogbackMain |
%file、%F | 日志调用所在文件,如: com.dragon.study.log.Log4j2Main Log4j2Main.java |
%method、%M | 日志所在方法,如: main |
%thread、%t | 日志调用所有线程序,如:main |
%level、%p | 日志级别,如:INFO |
%date、%d | 日期,如: 2018-12-15 21:40:12,890 |
%msg、%m | 日志记录内容 |
%processId、%pid | 进程号 |
%threadId、%tid | 线程号 |
%exception、%ex | 异常记录 |
%n | 行分隔 |
特殊占位符 | |
%X{user} | 表示可以获取外部自定义传入的值, 如:%X{user} =》org.slf4j.MDC.put("user", "xx-yy-zz"); |
宽度设置 | 可以限制上面的ConversationWord的宽度和左右对齐 |
%20logger | 最小宽度20,当小于20时,则左侧留空白。右对齐 |
%-20logger | 最小宽度20,当小于20时,则右侧留空白。左对齐 |
%.30logger | 最大宽度30,超出时从头部开始截断。如:%.2、test=》st |
%.-30logger | 最大宽度30,超出时从末尾开始截断。如:%.-2、test=》te |
%20.30logger | 最小20,最大30,小于20的时候右对齐,大于30的时候开始处截断 |
%-20.30logger | 最小20,最大30,小于20的时候左对齐,大于30的时候开始处截断 |
显示设置 | |
%highlight() | 突出显示,如:%highlight(%-5level) |
%green(%red、%blue、%white) | 字体显示为指定颜色 |
{length} | 可指定长度,如:%logger{36} |
JSONLayout 表示日志以JSON的格式输出,如下是常用参数,其他参数请参考官网
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
<JsonLayout />
<TimeBasedTriggeringPolicy />
RollingFile>
{
"timeMillis" : 1550798980964,
"thread" : "main",
"level" : "INFO",
"loggerName" : "com.cimu.Log4j2Test",
"message" : "log4j2",
"endOfBatch" : true,
"loggerFqcn" : "org.apache.logging.log4j.spi.AbstractLogger"
}
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
<JSONLayout complete="true" compact="true" eventEol="true" locationInfo="true" />
<TimeBasedTriggeringPolicy />
RollingFile>
{"timeMillis":1550799247113,"thread":"main","level":"INFO","loggerName":"com.cimu.Log4j2Test","message":"log4j2","endOfBatch":true,"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger"}
注意:提醒使用JsonLayout是需要依赖Jackson包的(如何使使用SpringBoot的话web中自带了Jackson包)
还有其他Lyaout就不一一介绍了,具体用到可以参考官网
Log4j2的Policy触发策略与Strategy滚动策略配置详解
Cron
表达式的触发策略,很灵活这两个Strategy都是控制如何进行日志滚动的,平时大部分用DefaultRolloverStrategy就可以了
官网详细介绍:http://logging.apache.org/log4j/2.x/performance.html
Log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益。上述学习日志默认都是同步的,同步就是当输出日志时,必须等待日志输出语句执行完毕后,才能执行后面的业务逻辑语句。异步日志就是分配单独的线程做日志记录。
同步日志:
请求进入 »»» 全局filter进行Level过滤 »»» Logger自身filter进行Level过滤 »»» 生成日志输出Message
↓
输出日志 ««« PatternLayout格式化LogEvent ««« Logger自身filter进行Level过滤 ««« 生成Logevent
异步日志:
请求进入 »»» 全局filter进行Level过滤 »»» Logger自身filter进行Level过滤 »»» 生成日志输出Message
输出日志 ««« Appender ──┐ ↓
输出日志 ««« Appender ──┤ ««« BlockingQueue ««« AsyncAppender
输出日志 ««« Appender ──┘
Log4j2 异步提供了两种实现日志的方式,一个是通过AsyncAppender【几乎没人用】,一个是通过AsyncLogger【主要是这个】,分别对应前面我们说的Appender组件和Logger组件。【注意:AsyncLogger是基于Disruptor实现,所以必须引入com.lmax.disruptor依赖】
这是两种不同的实现方式,在设计和源码上都是不同的体现,可以将这两种实现日志的方式看做完全不一样。
Log4j2 异步日志在如上两种实现方式的基础上,又分为全局异步和混合异步2种方式
1、AsyncAppender 方式(实际中用的少,因为性能低下)
AsyncAppender 是通过引用别的 Appender 来实现的,当有日志事件到达时,会开启另外一个线程来处理他们。AsyncAppender默认使用Java中自带的类库(util类库),不需要导入外部类库。AsyncAppender应该是在它引用Appender之后配置,因为如果在Appender的时候出现异常,对应用来说是无法感知的。当使用此 Appender的时候,在多线程的环境需要注意,阻塞队列容易受到锁争用的影响,这可能会对性能产生影响。这时我们需要使用无锁的异步记录器AsyncLogger
2、AsyncLogger 方式(实际中用的多,因为性能高)
AsyncAppender :性能提升较小,不推荐使用
使用AsyncAppender方式实现的全局异步日志输出
1、在 log4j.xml配置文件的 Appenders标签中,对于异步进行配置,使用Async标签
<Configuration status="warn" monitorinterval="10" name="myApp">
<Appenders>
<Console name="consoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%m%n"/>
Console>
<Async name="asyncConsole">
<AppenderRef ref="consoleAppender"/>
Async>
Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="asyncConsole"/>
Root>
Loggers>
Configuration>
2、编写测试代码,测试全局异步输出日志信息
package com.xyz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4j2Test {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
// 日志的记录
for (int i = 0; i < 3; i++) {
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
// 系统业务逻辑
for (int i = 0; i < 3; i++) {
// 这里必须使用System.out输出,如果单独使用logger.xx输出看不出来效果
logger.error("-----start-----");
System.out.println("-----sys-----"); // 实际上只有这一行是业务代码
logger.error("-----end-----");
}
}
}
3、输出结果(观察结果可以发现,此时实现了异步日志的输出)
-----sys-----
-----sys-----
-----sys-----
error信息
error信息
error信息
-----start-----
-----end-----
-----start-----
-----end-----
-----start-----
-----end-----
4、实际上可以得出结论:Log4j2的异步只是把打印日志单独提出一个线程(日志的打印还是顺序的),而业务的代码在主线程上先执行了。
使用AsyncAppender方式实现同时在应用中使用同步日志和异步日志输出。
1、在上面的基础上修改log4j.xml配置文件,自定义Logger-com.xyz,让其引用同步Appender,rootLogger依旧为异步的AsyncAppender
<Configuration status="warn" monitorinterval="10" name="myApp">
<Appenders>
<Console name="consoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%m%n"/>
Console>
<Async name="asyncConsole">
<AppenderRef ref="consoleAppender"/>
Async>
Appenders>
<Loggers>
<Logger name="com.xyz" level="error" includeLocation="false" additivity="false">
<AppenderRef ref="consoleAppender"/>
Logger>
<Root level="error">
<AppenderRef ref="asyncConsole"/>
Root>
Loggers>
Configuration>
2、编写测试代码,测试混合异步(同步和异步同时存在)输出日志信息(实际只测试了同步,想测试异步修改Logger对象包即可)
package com.xyz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4j2Test {
public static void main(String[] args) {
// 当前测试的是自定义的com.xyz的同步logger,如果想测试异步只需修改成其他名称即可
Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
// 日志的记录
for (int i = 0; i < 3; i++) {
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
// 系统业务逻辑
for (int i = 0; i < 3; i++) {
// 这里必须使用System.out输出,如果单独使用logger.xx输出看不出来效果
logger.error("-----start-----");
System.out.println("-----sys-----"); // 实际上只有这一行是业务代码
logger.error("-----end-----");
}
}
}
3、输出结果(观察结果可以发现,此时打印结果是按顺序执行的,这就代表实现了混合异步输出,这里没有重复再次测试异步输出,可自行测试)
error信息
error信息
error信息
-----start-----
-----sys-----
-----end-----
-----start-----
-----sys-----
-----end-----
-----start-----
-----sys-----
-----end-----
AsyncLogger:性能提升较大,推荐使用
1、全局异步 :所有的日志都是异步的日志记录,在配置文件上不用做任何的改动。有如下三种配置方式。
全局启用异步Logger方案一 :JVM启动加上如下参数
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
全局启用异步Logger方案二:classpath中添加文件log4j2.component.properties
,文件增加以下内容:
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
全局启用异步Logger方案三:如果是SpringBoot项目可以改为在启动类的地方修改
@SpringBootApplication
public class Log4j2Application {
public static void main(String[] args) {
System.setProperty("log4j2.contextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
SpringApplication.run(Log4j2Application.class, args);
}
}
2、使用AsyncLogger实现异步日志必须要要引入Disruptor依赖,不然开启全局异步或者使用到AsyncLogger的时候都会报错。
<dependency>
<groupId>com.lmaxgroupId>
<artifactId>disruptorartifactId>
<version>3.3.7version>
dependency>
3、正常在log4j.xml配置文件增加日志输出配置(配置文件与同步配置无区别)
<Configuration status="warn" monitorinterval="10" name="myApp">
<Appenders>
<Console name="consoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%m%n"/>
Console>
Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="consoleAppender"/>
Root>
Loggers>
Configuration>
4、编写测试代码,测试全局异步输出日志信息
package com.xyz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4j2Test {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
// 日志的记录
for (int i = 0; i < 3; i++) {
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
// 系统业务逻辑
for (int i = 0; i < 3; i++) {
// 这里必须使用System.out输出,如果单独使用logger.xx输出看不出来效果
logger.error("-----start-----");
System.out.println("-----sys-----"); // 实际上只有这一行是业务代码
logger.error("-----end-----");
}
}
}
5、输出结果(观察结果可以发现,此时实现了异步日志的输出)
-----sys-----
-----sys-----
-----sys-----
error信息
error信息
error信息
-----start-----
-----end-----
-----start-----
-----end-----
-----start-----
-----end-----
混合异步:就是同时在应用中使用同步日志和异步日志,这使得日志的配置方式更加灵活,
需求:自定义一个异步Logger–com.xyz(当前包下的日志输出都为异步的),设置rootLogger是同步的(其他都为同步输出)
注意:在使用混合异步模式时,一定要将全局的异步配置注释掉(resources下的properties)
#Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
1、修改log4j2.xml文件中的配置信息,增加异步Logger(AsyncLogger),如下配置:com.xyz 日志是异步的, root 日志是同步的
<Configuration status="warn" monitorinterval="10" name="myApp">
<Appenders>
<Console name="consoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%m%n"/>
Console>
Appenders>
<Loggers>
<AsyncLogger name="com.xyz" level="error" includeLocation="false" additivity="false">
<AppenderRef ref="consoleAppender"/>
AsyncLogger>
<Root level="error">
<AppenderRef ref="consoleAppender"/>
Root>
Loggers>
Configuration>
2、异步日志输出:编写测试代码,测试混合异步输出日志信息,这里先测试异步日志输出,使用对应设置的Logger即可
package com.xyz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4j2Test {
public static void main(String[] args) {
// 当前测试的是自定义的com.xyz的异步logger,如果想测试同步只需修改成其他名称即可
Logger logger = LoggerFactory.getLogger("com.xyz");
// 日志的记录
for (int i = 0; i < 3; i++) {
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
// 系统业务逻辑
for (int i = 0; i < 3; i++) {
// 这里必须使用System.out输出,如果单独使用logger.xx输出看不出来效果
logger.error("-----start-----");
System.out.println("-----sys-----");
logger.error("-----end-----");
}
}
}
3、输出结果(观察结果可以发现,此时实现了异步日志的输出)
-----sys-----
-----sys-----
-----sys-----
error信息
error信息
error信息
-----start-----
-----end-----
-----start-----
-----end-----
-----start-----
-----end-----
4、同步日志输出:编写测试代码,测试混合异步输出日志信息,这里测试同步日志输出,使用RootLogger即可(实际就修改定义的Logger)
// 当前测试使用的是RootLogger,RootLogger设置的是同步日志输出
Logger logger = LoggerFactory.getLogger("com");
5、输出结果(观察结果可以发现,此时实现了同步日志的输出)
error信息
error信息
error信息
-----start-----
-----sys-----
-----end-----
-----start-----
-----sys-----
-----end-----
-----start-----
-----sys-----
-----end-----
AsyncRoot 实际就是根 AsyncLogger,其用法及功能与 AsyncLogger 一摸一样(全局混合异步),参考上面的 AsyncLogger 即可。
总结一下Log4j2 异步化日志的三种方式:
全局启用异步Logger方案一:JVM启动参数(boot.ini)加上如下参数
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
全局启用异步Logger方案二:classpath中添加文件“log4j2.component.properties”,文件增加以下内容:
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
手工指定部分Logger采用异步方式:log4j2.xml配置文件中使用AsyncRoot 和 AsyncLogger 替代 Root 和 Logger
(上述3种方式任选其一即可,不要同时采用)
使用异步日志需要注意的问题:
通过log.info("是否为全局异步日志:{}", AsyncLoggerContextSelector.isSelected());
可以查看是否为全局异步日志
import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
import org.slf4j.LoggerFactory;
LoggerFactory.getLogger("com").error("com是否为异步日志:{}", AsyncLoggerContextSelector.isSelected());
LoggerFactory.getLogger("com.xyz").error("xyz是否为异步日志:{}", AsyncLoggerContextSelector.isSelected());
Log4j2 最牛的地方在于异步输出日志时的性能表现,Log4j2 在多线程的环境下吞吐量与 Log4j 和 Logback 的比较可查官网图片。三种模式对比:
日志输出类型 | 日志输出方式 |
---|---|
sync | 同步打印日志,日志输出与业务逻辑在同一线程内,当日志输出完毕,才能进行后续业务逻辑操作 |
AsyncAppender | 异步打印日志,内部采用ArrayBlockingQueue,对每个AsyncAppender创建一个线程用于处理日志输出 |
AsyncLogger | 异步打印日志,采用了高性能并发框架Disruptor,创建一个线程用于处理日志输出(要引入com.lmax.disruptor) |
sping-boot-starter 与 spring-boot-starter-web 都依赖 sping-boot-starter-looging
而SpringBoot底层默认使用 SLF4J+ Logback 方式进行日志记录,SpringBoot 使用中间替换包把其的日志框架都替换成了SLF4J
SpringBoot 能自动适配所有的日志框架,引入其他框架时,只需要把sping-boot-starter-looging依赖的日志框架排除掉即可
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-loggingartifactId>
<groupId>org.springframework.bootgroupId>
exclusion>
exclusions>
dependency>
<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>
官网配置详解(适用Logback与Log4j2)https://docs.spring.io/spring-boot/docs/2.5.0/reference/htmlsingle/#application-properties.core
日志的级别由低到高分别为:trace(跟踪) < debug(调试) < info(信息) < warn(警告) < error(错误)
在配置文件中调整输出的日志级别,日志就只会在这个级别及以后的高级别生效,SpringBoot 默认使用 INFO 级别(Log4j2 默认 ERROR)
SpringBoot 的全局配置文件 “application.properties” 或 “application.yml” 中可以修改日志配置项:
# 默认名log4j2-spring.xml,如果要设置其他名称则需要如下配置
# logging.config=classpath:log4j2-spring.xml
# 设置根节点的日志级别输出,root表示整个项目
logging.level.root=INFO
# 指定特定包及类的日志输出级别,未指定就按root设置的级别输出,如果root也未指定则按SpringBoot的默认级别info输出
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate=ERROR
logging.level.com.xyz=DEBUG
# 配置日志输出的文件,这两个选一个配置就可以了,一起配置的话,name的生效. 每次启动都是追加日志
# .name=具体文件, 写入指定的日志文件.文件名可以是确切的位置,也可以是相对于当前目录的位置
# .path=具体目录, 写入spring.log文件指定目录.目录名可以是确切的位置,也可以是相对于当前目录的位置
logging.file.name=logs/log4j2.log
logging.file.path=logs/
# 指定控制台输出的日志格式,例如: %d{yyyy-MM-dd HH:mm:ss} -- [%thread] %-5level %logger{50} %msg%n
# 1、%d 表示日期时间,
# 2、%thread 表示线程名,
# 3、%‐5level 级别从左显示5个字符宽度
# 4、%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
# 5、%msg 日志消息,
# 6、%n 换行符
# 7、%line 显示日志输出位置的行号,方便寻找位置
# 8、%X 特殊占位符,%X{uu_id}是获取uu_id的值,代码中设置uu_id值:org.slf4j.MDC.put("uuid", "xx-yy-zz");
# 配置日志输出格式, .file是配置输出到文件的日志格式, .console是配置输出到控制台的日志格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{50}:%line %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm} -- [%thread] %-5level %logger{50} %msg%n
# 设置日志记录器组,将相关的记录器组合在一起,然后设置日志记录器组的日志级别为TRACE
logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat
logging.level.tomcat=TRACE
# SpringBoot包括以下预定义的日志记录组,可以开箱即用
# web: org.springframework.core.codec, org.springframework.http, org.springframework.web
# sql: org.springframework.jdbc.core, org.hibernate.SQL
logging.level.web=INFO
logging.level.sql=DEBUG
- 默认情况,日志文件超过10M时,会新建文件进行递增,如logback.log、logback1.log、logback2.log,使用logging.file.max-size更改大小限制
- 默认情况,日志只记录到控制台,不写入日志文件,如果要在控制台输出之外写入到日志文件,则需要设置 .file 或 .path 属性
- 默认情况,logging.file.* 等配置使用的都是RollingFileAppender
SpringBoot默认是用Logback日志框架,需要排除Logback,不然会出现jar依赖冲突报错(spring-boot 2.5.2 + log4j 2.14.1 + JDK 1.8)
<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>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
如果自定义了Log4j2文件名,需要在application.properties中配置( 默认名log4j2-spring.xml,就省下了在application.yml中配置),如果只引入了 log4j2 依赖,没有提供 log4j2.xml 配置文件,则默认日志输出级别为 INFO,会打印在控制台,但是不会记录到文件中。实际上可以不使用Log4j2配置文件,只用SpringBoot配置,不过这样能使用的功能是非常有限的(如果是这样还不如使用Logback)
logging.config=classpath:log4j2-spring.xml
log4j2-spring.xml(SpringBoot会自动加载-spring的文件,所以可不配置logging.config),如果要自定义名称则需要配置logging.config
<configuration status="WARN" monitorInterval="30">
<properties>
<property name="LOG_HOME">./WebAppLogs/logsproperty>
properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{1.} - %m%n"/>
console>
<RollingFile name="RollingFileTrace" immediateFlush="true" fileName="${LOG_HOME}/trace.log"
filePattern="${LOG_HOME}/trace_%d{yyyy-MM-dd-HH}-%i.log.zip">
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
Policies>
<DefaultRolloverStrategy max="20">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="trace_*.zip"/>
<IfLastModified age="168H"/>
Delete>
DefaultRolloverStrategy>
RollingFile>
<RollingFile name="RollingFileDebug" immediateFlush="true" fileName="${LOG_HOME}/debug.log"
filePattern="${LOG_HOME}/debug_%d{yyyy-MM-dd-HH}-%i.log.zip">
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
Policies>
<DefaultRolloverStrategy max="20">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="debug_*.zip"/>
<IfLastModified age="168H"/>
Delete>
DefaultRolloverStrategy>
RollingFile>
<RollingFile name="RollingFileInfo" immediateFlush="true" fileName="${LOG_HOME}/info.log"
filePattern="${LOG_HOME}/info_%d{yyyy-MM-dd-HH}-%i.log.zip">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
Policies>
<DefaultRolloverStrategy max="20">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="info_*.zip"/>
<IfLastModified age="168H"/>
Delete>
DefaultRolloverStrategy>
RollingFile>
<RollingFile name="RollingFileWarn" immediateFlush="true" fileName="${LOG_HOME}/warn.log"
filePattern="${LOG_HOME}/warn_%d{yyyy-MM-dd-HH}-%i.log.zip">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
Policies>
<DefaultRolloverStrategy max="20">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="warn_*.zip"/>
<IfLastModified age="168H"/>
Delete>
DefaultRolloverStrategy>
RollingFile>
<RollingFile name="RollingFileError" immediateFlush="true" fileName="${LOG_HOME}/error.log"
filePattern="${LOG_HOME}/error_%d{yyyy-MM-dd-HH}-%i.log.zip">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
Policies>
<DefaultRolloverStrategy max="20">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="error_*.zip"/>
<IfLastModified age="168H"/>
Delete>
DefaultRolloverStrategy>
RollingFile>
appenders>
<loggers>
<logger name="org.springframework" level="INFO"/>
<logger name="org.mybatis" level="INFO"/>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileDebug"/>
<appender-ref ref="RollingFileTrace"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
root>
loggers>
configuration>
Log4j2配置详解请参考前面的教程【自定义配置文件】
这是转载其他人整理的百分号属性参数说明大全:
********************************************************************************************************************
参数 说明 举例 输出显示媒介
********************************************************************************************************************
%c 列出logger名字空间的全称,如果加上{<层数>}, 假设当前logger的命名空间是"a.b.c"
则表示列出从最内层算起的指定层数的名字空间
%c a.b.c
%c{2} b.c
%20c (若名字空间长度小于20,则左边用空格填充)
%-20c (若名字空间长度小于20,则右边用空格填充)
%.30c (若名字空间长度超过30,截去多余字符)
%20.30c (若名字空间长度小于20,则左边用空格填充;
若名字空间长度超过30,截去多余字符)
%-20.30c (若名字空间长度小于20,则右边用空格填充;
若名字空间长度超过30,截去多余字符)
********************************************************************************************************************
%C 列出调用logger的类的全名(包含包路径) 假设当前类是"org.apache.xyz.SomeClass"
%C org.apache.xyz.SomeClass
%C{1} SomeClass
%class
********************************************************************************************************************
%d 显示日志记录时间,{<日期格式>}使用ISO8601定义的日期格式
%d{yyyy/MM/dd HH:mm:ss,SSS} 2005/10/12 22:23:30,117
%d{ABSOLUTE} 22:23:30,117
%d{DATE} 12 Oct 2005 22:23:30,117
%d{ISO8601} 2005-10-12 22:23:30,117
********************************************************************************************************************
%F 显示调用logger的源文件名 %F MyClass.java
********************************************************************************************************************
%l 显示日志事件的发生位置,包含包路径、方法名、
源文件名,以及在代码中的行数 %l com.a.b.MyClass.main(MyClass.java:168)
********************************************************************************************************************
%L 显示调用logger的代码行 %L 129
%line %line 129
********************************************************************************************************************
%level 显示该条日志的优先级 %level INFO
%p %p INFO
********************************************************************************************************************
%m 显示输出消息 %m This is a message for debug.
%message %message This is a message for debug.
********************************************************************************************************************
%M 显示调用logger的方法名 %M main
********************************************************************************************************************
%n 当前平台下的换行符 %n Windows平台下表示rn,UNIX平台下表示n
********************************************************************************************************************
%p 显示该条日志的优先级 %p INFO
%level %level INFO
********************************************************************************************************************
%r 显示从程序启动时到记录该条日志时已经经过的毫秒数 %r 1215
********************************************************************************************************************
%t 输出产生该日志事件的线程名 %t http-nio-8080-exec-10
%thread %thread http-nio-8080-exec-10
********************************************************************************************************************
%x 按NDC(Nested Diagnostic Context,线程堆栈)顺序输出日志 假设某程序调用顺序是MyApp调用com.foo.Bar
%c %x - %m%n MyApp - Call com.foo.Bar.
com.foo.Bar - Log in Bar
MyApp - Return to MyApp.
********************************************************************************************************************
%X 按MDC(Mapped Diagnostic Context,线程映射表)
输出日志。通常用于多个客户端连接同一台服务器,
方便服务器区分是那个客户端访问留下来的日志。 %X{5} (记录代号为5的客户端的日志)
********************************************************************************************************************
%% 显示一个百分号 %% %
********************************************************************************************************************
如下使用 slf4j + log4j2 配合打印日志,当前方式也是比较流行的,slf4j 能适配各种日志框架,迁移起来也会非常方便,功能上会log4j2稍逊。
@Test
void slf4jTest() {
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger("slf4jTest");
log.error("hello, I am error!");
log.warn("hello, I am warn!");
log.info("hello, I am {}!", "info");
log.debug("hello, I am debug!");
log.trace("hello, I am trace!");
}
日志实现 Slf4j 自定义工具类:
public class Slf4jLogUtil {
/**
* debug级别日志输出
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void debug(Class clazz, String msg, Object... params) {
Logger logger = LoggerFactory.getLogger(clazz.getName());
logger.debug(msg, params);
}
/**
* trace日志输出
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void trace(Class clazz, String msg, Object... params) {
Logger logger = LoggerFactory.getLogger(clazz.getName());
logger.trace(msg, params);
}
/**
* info级别日志输出
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void info(Class clazz, String msg, Object... params) {
Logger logger = LoggerFactory.getLogger(clazz.getName());
logger.info(msg, params);
}
/**
* warn级别日志输出
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void warn(Class clazz, String msg, Object... params) {
Logger logger = LoggerFactory.getLogger(clazz);
logger.warn(msg, params);
}
/**
* error级别日志输出
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void error(Class clazz, String msg, Object... params) {
Logger logger = LoggerFactory.getLogger(clazz);
logger.error(msg, params);
}
}
@Test
void log4j2Test() {
org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger("log4j2Test");
log.error("hello, I am error!");
log.warn("hello, I am warn!");
log.info("hello, I am {}!", "info");
log.debug("hello, I am debug!");
log.trace("hello, I am trace!");
}
日志实现 Log4j2 自定义工具类:
public class Log4j2LogUtil {
/**
* debug级别日志输出
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void debug(Class clazz, String msg, Object... params) {
Logger logger = LogManager.getLogger(clazz.getName());
logger.debug(msg, params);
}
/**
* trace级别日志输出
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void trace(Class clazz, String msg, Object... params) {
Logger logger = LogManager.getLogger(clazz.getName());
logger.trace(msg, params);
}
/**
* info级别日志输出
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void info(Class clazz, String msg, Object... params) {
Logger logger = LogManager.getLogger(clazz.getName());
logger.info(msg, params);
}
/**
* warn级别日志输出
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void warn(Class clazz, String msg, Object... params) {
Logger logger = LogManager.getLogger(clazz);
logger.warn(msg, params);
}
/**
* error级别日志输出
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void error(Class clazz, String msg, Object... params) {
Logger logger = LogManager.getLogger(clazz);
logger.error(msg, params);
}
/**
* fatal日志
* @param clazz 类
* @param msg 日志
* @param params 其他参数
*/
public static void fatal(Class clazz, String msg, Object... params) {
Logger logger = LogManager.getLogger(clazz);
logger.fatal(msg, params);
}
}
建议使用 SLF4J门面 + Log4j2门面及实现,搭配同时使用,一般情况打印日志的时候可以使用SLF4J之类的API,需要使用Log4j2更多强大功能时就使用自己的门面API即可(例如:参数格式化以及"惰性"打日志之类的功能)
package com.xyz;
public class Log4j2Test {
private static final org.slf4j.Logger slf4j = org.slf4j.LoggerFactory.getLogger(Log4j2Test.class);
private static final org.apache.logging.log4j.Logger log4j = org.apache.logging.log4j.LogManager.getLogger(Log4j2Test.class);
public static void main(String[] args) {
// 使用slf4j门面 + log4j实现
slf4j.info("test: {}", "xx");
// 使用log4j门面 + log4j实现
log4j.debug("test:{}",() -> "xx");
}
}
热加载配置文件是很有用的,比如服务上线之后,当需要输出详细日志信息排查问题时,可以降低优先级,比如:TRACE 、DEBUG,当完事之后,可以再调高优先级,比如 WARN、ERROR,减少日志记录,提供性能
Log4j2 与 Logback 一样,可以在修改后自动重新加载其配置,与 Logback 不同的是,它在进行重新配置时不会丢失日志事件
使用非常简单只需要在文件开始的 configuration 标签中指定 monitorInterval 属性即可,值大于 0 时会自动创建子线程进行定时扫描,更新配置;不配置或者值为0时,不会进行监控,运行中修改是无效的:
<configuration status="WARN" monitorInterval="60">
如果是在本地 IDE 编辑器中测试,注意修改的是 classes 编译目录下的 log4j2.xml 文件,而不是 resources 目录的源文件
对于可以手动修改配置文件的时候,此种方式还是很方便的,但是如果应用是打成 .jar、.war 包发布部署,则这种方式还是不太方便,因为仍然得从生产下载 jar、war 包,然后修改配置,重启服务
比较推荐的一种使用方式
package com.xyz;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class Log4j2Controller {
private static final Logger log = LogManager.getLogger(Log4j2Controller.class);
/**
* 打印日志测试: http://localhost:8080/logback/logs
*/
@GetMapping("logback/logs")
public void logs() {
log.error("hello, I am error!");
log.warn("hello, I am warn!");
log.info("hello, I am {}!", "info");
log.debug("hello, I am debug!");
log.trace("hello, I am trace!");
}
/**
* Log4j2 动态修改 root(根)日志级别,修改之后配置立刻生效.
* http://localhost:8080/log4j/setRootLevel?level=TRACE
*
* @param level :可选值有:ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF,否则默认会设置为 DEBUG,不区分大小写。
* @return
*/
@GetMapping(value = "/log4j/setRootLevel")
public Map<String, Object> setRootLevel(@RequestParam(value = "level") String level) {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("code", 200);
dataMap.put("msg", "修改root日志级别成功");
try {
// LoggerContext getContext(final boolean currentContext):获取 log4j 日志上下文,false 表示返回合适调用方的上下文
LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
// 返回当前配置,发生重新配置时,将替换该配置。
Configuration configuration = loggerContext.getConfiguration();
// 查找记录器名称的相应 LoggerConfig
LoggerConfig loggerConfig = configuration.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
// 设置日志级别,如果 level 值不属于 ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF,则默认会设置为 DEBUG
Level toLevel = Level.toLevel(level);
loggerConfig.setLevel(toLevel);
// 根据当前配置更新所有记录器
loggerContext.updateLoggers();
// 查询 root(根)日志输出级别结果返回
String root_logger_name = LogManager.getRootLogger().getLevel().name();
dataMap.put("data", root_logger_name);
} catch (Exception e) {
dataMap.put("code", 500);
dataMap.put("msg", e.getMessage());
e.printStackTrace();
}
return dataMap;
}
}
使用SpringBoot Actuator监控管理日志的优点:
1、引入pring Boot Actuator依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
2、开启日志访问端点: /loggers
management:
endpoint:
health:
show-details: ALWAYS # 展示节点的详细信息
endpoints:
web:
exposure:
include: info,health,logfile,loggers # 指定公开的访问端点
3、查看级别
4、修改日志级别
错误1:“Logging system failed to initialize using configuration from 'classpath:log4j2.xml”
错误原因:引用jar时引用了多个logback的框架,由于idea开发工具未根据pom.xml的配置自动配置依赖,未去除之前的log4j的依赖,导致无法解析节点信息
解决办法:在pom.xml文件中右击选择Diagrams,查看依赖图,找到spring.boot.starter-logging依赖,删除即可。
错误2:“java.lang.ClassNotFoundException: com.lmax.disruptor.EventFactory”
解决方法:修改配置
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
<dependency>
<groupId>com.lmaxgroupId>
<artifactId>disruptorartifactId>
<version>3.4.1version>
dependency>
如下是参考微信公众号文章:
如下是Log4j2自定义组件文章: