log4j2
依赖
需要排除掉springboot自带的logback和logging的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
<exclusion>
<!--log4j-slf4j-impl与 logback-classic包不兼容,删除这个包 -->
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <!-- 引入log4j2依赖 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency> <!-- 异步依赖 -->
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
XML配置
在resources下创建log4j2.xml
XML大致标签架构(表达的比较直白)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="5">
<Properties>
全局变量
</Properties>
<Appenders>
<console >项目控制台设置</console>
<RollingRandomAccessFile >可归档的日志设置(推荐)</RollingRandomAccessFile >
<RollingFile >可归档的日志设置</RollingFile>
</Appenders>
<Loggers>
<Logger >部分日志设置</Logger>
<AsyncLogger >异步日志</AsyncLogger>
<Root >
<AppenderRef />全局日志
</Root>
</Loggers>
</Configuration>
Configuration
level日志级别: ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
<Configuration status="WARN" status="info" monitorInterval="5">
monitorInterval:xx秒 当xml变化时无需重启,热更新设置
status:Log4j2内部日志的输出级别
Properties 全局变量配置
全局变量配置 ${sys:catalina.home}为tomcat部署路径,例如:/data/tomcat。
<Properties>
<!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<!--
%[format]conversion{param}
% 必须,起始符号。
[format] 可选,部分conversion需要,用于指定输出宽度,对齐方式等
conversion 必须,转换符号
param 可选,部分转换符号需要指定参数,如日期格式等
例如:
%d{yyyy-MM-dd HH:mm:ss} 打印当前时间
%thread表示线程名
%-5level 打印日记级别,并且占5个字符宽度
%class{36} class路径
%M 打印方法名称
%logger{360} 表示 Logger 名字最长36个字符
-->
<property name="LOG_PATTERN" value="%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{360} - %msg%n" />
<!-- 定义日志存储的路径,不要配置相对路径 -->
<property name="FILE_PATH" value="/logs" />
<property name="FILE_NAME" value="myApp" />
</Properties>
Appenders 日志定义
定义日志输出目的地,内容和格式等
console 控制台输出
<!--控制台输出日志的格式 target:SYSTEM_OUT 或 SYSTEM_ERR===============================-->
<console name="Console" target="SYSTEM_OUT">
<!--PatternLayout:输出格式,不设置默认为:%m%n.-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="OFF" onMatch="ACCEPT" onMismatch="DENY"/>
</console>
File 自定义所有log信息
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用====-->
<File name="Filelog" fileName="${FILE_PATH}/application.log" append="false">
<PatternLayout pattern="${LOG_PATTERN}"/>
</File>
RollingRandomAccessFile
输出日志配置
- name:Appender名称在标签内引用
- immediateFlush:log4j2接收到日志事件时,是否立即将日志刷到磁盘。默认为true。
- fileName:日志存储路径
- filePattern:历史日志封存路径。其中%d{yyyyMMddHH}表示了日志的时间单位,log4j2自动识别zip等后缀,表示历史日志需要压缩。
- immediateFlush:磁盘刷新方式 设置为false效率很高,但是日志存储会丢失亲测1w数据丢失3000+
<RollingRandomAccessFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" immediateFlush="false"
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
日志过滤
- level,表示最低接受的日志级别,博客配置的为INFO,即我们期望打印INFO级别以上的日志。
- onMatch,表示匹配该级别及以上(>=)一般为ACCEPT=接受。
- onMismatch,表示匹配该级别以下 (<)。一般为DENY=拒绝。NEUTRAL=中立。
ThresholdFilter 阈值过滤器(级别)
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
TimeFilter 时间过滤
<TimeFilter start="08:00:00" end="08:30:00" onMatch="ACCEPT" onMismatch="DENY" />
RegexFilter 匹配过滤
<RegexFilter regex=".* test .*" onMatch="NEUTRAL" onMismatch="DENY"/>
MarkerFilter 高亮过滤
<MarkerFilter marker="testError" onMatch="ACCEPT" onMismatch="DENY"/>
使用方法
private static Logger logger = LoggerFactory.getLogger(xxx.class);
private static final Marker MARKER = MarkerFactory.getMarker("testError");
public static void main(String...strings) {
logger.error(MARKER, "Test message~");
logger.error("没有指定marker,该条日志会被过滤掉");
}
嵌套Filters 可实现只接收info级别(无warn 无error)
<Filters>
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
PatternLayout 日志格式
%[format]conversion{param}
% 必须,起始符号。
[format] 可选,部分conversion需要,用于指定输出宽度,对齐方式等
conversion 必须,转换符号
param 可选,部分转换符号需要指定参数,如日期格式等
例如:
%d{yyyy-MM-dd HH:mm:ss} 打印当前时间
%thread表示线程名
%-5level 打印日记级别,并且占5个字符宽度
%class{36} class路径
%M 打印方法名称
%logger{360} 表示 Logger 名字最长36个字符
引用全局变量
<PatternLayout pattern="${LOG_PATTERN}"/>
Policies 归档设置
<Policies> <!--归档设置-->
<OnStartupTriggeringPolicy minSize="1"/>
<TimeBasedTriggeringPolicy interval="10" modulate="false" />
<SizeBasedTriggeringPolicy size="100MB"/>
<CronTriggeringPolicy schedule="0 0 8 * * ?"/>
</Policies>
OnStartupTriggeringPolicy 启动应用程序时触发归档动作
minSize:文件大小(字节数),minSize=0,强制创建一个空的归档文件。
<OnStartupTriggeringPolicy minSize="1"/>
TimeBasedTriggeringPolicy 按时间间隔归档
interval=时间间隔 单位由filePattern的%d日期格式指定
%d{yyyy-MM-dd-HH} 匹配单位是时
%d{yyyy-MM-dd}匹配单位是日
%d{yyyy-MM-dd HHmmss}匹配单位是秒
modulate=boolean 是否对interval取模,决定了下一次触发的时间点
以小时举例,当前系统时间是上午3点,interval是4,
如果modulate=true,那么上午4.8.12...点触发归档,()
如果modulate=false,那么上午7.11...点触发下一次归档,之后每间隔4小时触发一次归档
maxRandomDelay=单位:秒,下一次触发时间会在interval基础上,增加一个随机的毫秒数Random.nextLong(0, 1+maxRandomDelay*1000)
<TimeBasedTriggeringPolicy interval="10" modulate="false" />
SizeBasedTriggeringPolicy 按照日志文件的大小
size:当前日志文件的最大size,支持单位:KB/MB/GB
<SizeBasedTriggeringPolicy size="100MB"/>
CronTriggeringPolicy 按照时间间隔来触发
schedule:cron语法 evaluateOnStartup=true,
在TriggeringPolicy初始时评估是否需要补充一次归档;false,不检测
<CronTriggeringPolicy schedule="0 0 8 * * ?"/>
DefaultRolloverStrategy 历史日志配置
<!--保存24小时历史日志,但不想用文件索引
1. age的单位:D天、H小时、M分、S秒
2. basePath表示日志存储的基目录,maxDepth=“1”当前目录。其他maxDepth=2。
-->
<DefaultRolloverStrategy max="24">
<Delete basePath="${FILE_NAME}/${FILE_PATH}" maxDepth="2">
<IfFileName glob="*/msg.*.zip" />
<IfLastModified age="24H" />
</Delete>
</DefaultRolloverStrategy>
Loggers
Logger 日志配置
- name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点.
- AppenderRef:关联的Appender
- additivity:logEvent的传递性。true LogEvent处理后传递给父Logger打印。false LogEvent处理后不再向上传递给父Logger。
过滤掉mybatis的信息 显示spring信息
<Logger name="org.springframework" level="info" additivity="true">
<AppenderRef ref="Console"/>
</Logger>
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
</logger>
AsyncLogger 异步日志
添加依赖
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
includelocation="false" 关闭日志行号信息
<AsyncLogger name="asyncLoggerWarn" additivity="FALSE" level="warn" includelocation="false">
<AppenderRef ref="msgAppender" />
</AsyncLogger>
ROOT 日志默认打印到控制台
<Root level="info" includeLocation="true">
<AppenderRef ref="引用的日志配置名称"/>
</Root>
整体XML
配置同步info,warn
配置异步info,warn
高亮error
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="5">
<Properties>
<property name="LOG_PATTERN" value="%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{360} - %msg%n" />
<property name="FILE_PATH" value="/logs" />
<property name="FILE_NAME" value="myApp" />
</Properties>
<Appenders>
<!--控制台输出日志的格式 target:SYSTEM_OUT 或 SYSTEM_ERR===============================-->
<console name="Console" target="SYSTEM_OUT">
<!--PatternLayout:输出格式,不设置默认为:%m%n.-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="OFF" onMatch="ACCEPT" onMismatch="DENY"/>
</console>
<!-- ++++++++++++++++++ 配置Info ++++++++++++++++++++++++++++ -->
<RollingRandomAccessFile name="RollingRAF_info" fileName="${FILE_PATH}/info.log" immediateFlush="true"
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<Filters>
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT"/>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="ACCEPT"/>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="10" modulate="false" />
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy max="24"/>
</RollingRandomAccessFile>
<!-- ++++++++++++++++++ 配置warn ++++++++++++++++++++++++++++ -->
<RollingFile name="RollingRAF_warn" fileName="${FILE_PATH}/warn.log"
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<Filters>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- ++++++++++++++++++ 配置高亮的error ++++++++++++++++++++++++++++ -->
<RollingFile name="RollingMarkerFilter" fileName="${FILE_PATH}/MarkerError.log" filePattern="${FILE_PATH}/${FILE_NAME}-MarkerError-%d{yyyy-MM-dd}_%i.log.gz">
<MarkerFilter marker="MarkerError" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy max="15"/>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<logger name="org.mybatis"
level="info" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<!--AsyncLogger 异步日志 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-->
<AsyncLogger name="asyncLoggerInfo" additivity="FALSE" level="info">
<appender-ref ref="RollingRAF_info" />
</AsyncLogger>
<AsyncLogger name="asyncLoggerWarn" additivity="FALSE" level="warn">
<appender-ref ref="RollingRAF_warn" />
</AsyncLogger>
<Root level="info" includeLocation="true">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingRAF_info"/>
<AppenderRef ref="RollingRAF_warn"/>
<AppenderRef ref="RollingMarkerFilter"/>
</Root>
</Loggers>
</Configuration>
测试。。。。同步异步的坑
* 测试 100Th》for10 丢失
* 测试 10*10 OK
* 测试 100*1 丢失
· 亲测 RollingRandomAccessFile immediateFlush=false 同步异步都会丢失数据
· AsyncLogger 无论使用 RollingRandomAccessFile || RollingFile 都会丢失数据
只有同步并发 immediateFlush=true || RollingFile 数据量才是完整的
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.1</version>
</dependency>
import cn.hutool.core.lang.Console;
import cn.hutool.core.thread.ConcurrencyTester;
import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.*;
import org.junit.Test;
import org.slf4j.MarkerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
@SpringBootTest
@Slf4j
public class Log4j2DemoApplicationTests {
private static org.apache.logging.log4j.Logger logger = LogManager.getLogger(Log4j2DemoApplicationTests.class);
private static org.apache.logging.log4j.Logger asyncLoggerInfo = LogManager.getLogger("asyncLoggerInfo");
private static org.apache.logging.log4j.Logger asyncLoggerWarn = LogManager.getLogger("asyncLoggerWarn");
private static org.slf4j.Marker MARKER= MarkerFactory.getMarker("MarkerError");
public static HashMap<String,Object> map=null;
static{
map=new HashMap<>();
map.put("name","张三");
map.put("age","18");
}
/**
* 同步Info RollingRandomAccessFile
* 结果:数据量完整 用时:11200
*/
@Test
public void log4jSyncInfo() {
ConcurrencyTester tester = ThreadUtil.concurrencyTest(100, () -> {
for (int i = 0; i < 10000; i++) {
logger.info(map);
}
});
Console.log(tester.getInterval());
}
/**
* 同步warn RollingFile
* 结果:数据 用时:12386
*/
@Test
public void log4jSyncWarn() {
ConcurrencyTester tester = ThreadUtil.concurrencyTest(100, () -> {
for (int i = 0; i < 10000; i++) {
logger.warn(map);
}
});
Console.log(tester.getInterval());
}
/**
* 同异步info
* 结果:丢失26万 用时:4125
* 测试 100Th》for10 丢失
* 测试 10*10 OK
* 测试 100*1 丢失
*/
@Test
public void asyLog4j2Info() {
ConcurrencyTester tester = ThreadUtil.concurrencyTest(100, () -> {
for (int i = 0; i < 10000; i++) {
asyncLoggerInfo.info(map);
}
});
Console.log(tester.getInterval());
}
/**
* 同异步warn
* 结果:丢失26万 用时:3914
*/
@Test
public void asyLog4j2Warn() {
ConcurrencyTester tester = ThreadUtil.concurrencyTest(100, () -> {
for (int i = 0; i < 10000; i++) {
asyncLoggerWarn.warn(map);
}
});
Console.log(tester.getInterval());
}
@Test
public void MARKER() {
log.error(MARKER, "Test message~");
log.error("没有指定marker,该条日志会被过滤掉");
}
}