1. 描述
一个springboot项目使用 log4j2 记录程序运行过程中的日志, 配置 log4j2 生成控制台日志和文件日志记录,以及对文件日志以日期和大小进行拆分的 demo示例。
环境:
IDE(idea):2021.3
JDK:1.8
maven:3.8.4
spring boot:2.5.6
log4j-core: 2.14.1 (spring-boot-starter-log4j2: 2.56)
提示:
写这个demo的过程中遇到不少问题(写在最后)。 文档和源码都看得我有点懵,感觉比 logback 的上手难度大不少。
2. 结果
- 控制台日志格式:

- 文件日志格式:

- 日志拆分归档:eg: 日志文件最大10KB,总文件大小20KB,最长时间30天, 当前策略目录下总文件格式19, 满足条件的总文件数20(Delete配置,不止针对当前策略)


3. demo
用了个定时任务在打印日志, 输出的日志文件用zip压缩格式进行了归档。文件大小设置的较小,以便于测试。
3.0 项目结构

3.1 pom.xml
- 引入 log4j2 相关jar包(排除出web依赖中已有logging依赖)。
<!--提供全栈的 web 开发特性,包括 Spring MVC 依赖和 Tomcat 容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions><!-- 去掉默认配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- Required for AsyncLoggers -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
3.2 SpringBootApplication
- 使用了EnableScheduling 注解,启用定时任务。
@Slf4j
@EnableScheduling // 启用定时任务
@SpringBootApplication
public class DemoLogLog4j2Application {
public static void main(String[] args) {
ConfigurableEnvironment environment = SpringApplication.run(DemoLogLog4j2Application.class, args).getEnvironment();
String port = environment.getProperty("server.port");
String contextPath = environment.getProperty("server.servlet.context-path");
log.info("----- http://localhost:{}{} -----", port, contextPath);
}
}
3.3 application.yml配置
server:
port: 8880
servlet:
context-path: /demo
3.4 log4j2-spring.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<!--设置log4j2的自身log级别为warn-->
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<!--strict 默认false,xml使用strict风格时,需要配置strict="true" -->
<!--No need to set system property "log4j2.contextSelector" to any value when using <asyncLogger> or <asyncRoot>. -->
<configuration status="warn" monitorInterval="30" strict="false">
<!-- 全局参数 -->
<Properties>
<!-- 控制台日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="%d %pid ${LOG_LEVEL_PATTERN:-%5p} --- [%15.15t] %-40.40logger{39} %line : %m%n"/>
<!-- <property name="FILE_LOG_PATTERN"-->
<!-- value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} %line : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>-->
<!--格式化输出:%d表示日期,%thread或%t表示线程名,%-5level:级别从左显示5个字符宽度, %line: 行号,%msg或%m:日志消息,%n是换行符-->
<property name="FILE_LOG_PATTERN" value="%d %pid %-5level --- [%t] %-33.33logger{32} %line : %m%n"/>
<!-- 全局参数 -->
<property name="LOG_HOME" value="/var/logs"/>
<property name="LOG_NAME" value="demo_log_log4j2"/>
<property name="LOG_PATH">${LOG_HOME}/${LOG_NAME}</property>
<!-- 和上面的写法一个意思 -->
<!-- <property name="LOG_PATH" value="${LOG_HOME}/${LOG_NAME}"/> -->
</Properties>
<Appenders>
<!-- 日志输出:ConsoleAppender-控制台输出。 -->
<Console name="STDOUT" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${CONSOLE_LOG_PATTERN}"/>
</Console>
<!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
<!-- 日志输出:RollingFileAppender-日志文件输出:根据rollingPolicy(滚动策略)和TriggeringPolicy(触发策略)输出日志 -->
<RollingFile name="FILE_INFO" fileName="${LOG_PATH}/${LOG_NAME}.log"
immediateFlush="true" append="true"
filePattern="${LOG_PATH}/info/${LOG_NAME}_%d{yyyy-MM-dd}.part_%i.log.zip">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${FILE_LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="10KB"/>
</Policies>
<!--最多可以保存19个文件(max只对当前时间分片有效), 文件序号到达最大值后,会删除最早的文件,并对剩下的历史文件重命名-->
<DefaultRolloverStrategy max="19">
<!--可以删除任何文件,而不仅仅是滚动的日志文件!使用testMode参数,不删除,打印一条info级别日志信息-->
<Delete basePath="${LOG_PATH}" maxDepth="2">
<IfFileName glob="*/*.log.zip">
<IfAny>
<!-- age的单位:D、H、M、S,分别表示天、小时、分钟、秒。 可以混用,eg: P1513H -->
<IfLastModified age="P30D" />
<!-- 文件总大小 -->
<IfAccumulatedFileSize exceeds="10 KB" />
<!-- 文件总个数 -->
<IfAccumulatedFileCount exceeds="20" />
</IfAny>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
<!--最多可以保存10个文件, 不会对历史文件重命名, 序号会继续增加; 但是Appender不能指定fileName属性-->
<!-- <DirectWriteRolloverStrategy maxFiles="10"/>-->
</RollingFile>
<!-- 日志输出:RollingRandomAccessFile-日志文件输出:根据rollingPolicy(滚动策略)和TriggeringPolicy(触发策略)输出日志 -->
<!-- 始终处于缓冲状态(无法关闭),不支持文件锁定;-->
<RollingRandomAccessFile name="FILE_ERROR" fileName="${LOG_PATH}/${LOG_NAME}_error.log"
append="true"
filePattern="${LOG_PATH}/error/${LOG_NAME}_%d{yyyy-MM-dd}.part_%i.log.zip">
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${FILE_LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="10KB"/>
</Policies>
<!-- max只对当前时间分片有效 -->
<DefaultRolloverStrategy max="10">
<!-- <Delete basePath="${LOG_PATH}" maxDepth="2" testMode="true">-->
<!-- <IfFileName glob="*/*.log.zip" />-->
<!-- <IfLastModified age="10D" />-->
<!-- <IfAccumulatedFileCount exceeds="15" />-->
<!-- <IfAccumulatedFileSize exceeds="20KB" />-->
<!-- </Delete>-->
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<AsyncLogger name="com.demo" level="DEBUG" includeLocation="true">
<AppenderRef ref="FILE_INFO"/>
<AppenderRef ref="FILE_ERROR"/>
</AsyncLogger>
<!-- <logger name="com.demo" level="ERROR" includeLocation="true">-->
<!-- <AppenderRef ref="FILE_ERROR"/>-->
<!-- </logger>-->
<Root level="INFO">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="FILE_INFO"/>
<AppenderRef ref="FILE_ERROR"/>
</Root>
</Loggers>
</configuration>
3.5 其他代码
- SchedulingConfig.java
/**
* 定时任务配置
*
* @author byrc
* @date 2022/3/14
*/
@Configuration
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
//设定一个长度10的定时任务线程池, 多线程定时任务
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(9));
}
}
- DemoTask.java
@Slf4j
@Component
public class DemoTask {
@Scheduled(cron = "0/30 * * * * ?") //@Scheduled来创建定时任务 这个注解用来标注一个定时任务方法
public void action1() {
log.info("----- Drink water ------------------------");
}
@Scheduled(cron = "33 */1 * * * ?")
public void action2() {
log.debug("----- debug -------------");
log.info("----- info -----------");
log.warn("----- warn -------------");
log.error("----- error -------------");
}
@Scheduled(cron = "0/3 * * * * ?")
public void action3() {
log.debug("---action3-- debug ---1----------");
log.info("---action3-- info -----1------");
log.warn("---action3-- warn -----1--------");
log.error("--action3--- error ----1---------");
log.info("---action3-- info ----2-------");
log.warn("---action3-- warn -----2--------");
log.error("--action3--- error --2-----------");
log.error("--action3--- error -----ADSL飞洒发的---");
}
4. 资料
- log4j2 官网:https://logging.apache.org/log4j/2.x/
- log4j2 github:https://github.com/apache/logging-log4j2
5. 注
- spirng boot 已在父项目引入(父项目pom.xml配置);
- 部分jar包版本已在父项目管理(如果对应不上,一定、肯定、决定是某些修改,没同步更新文档)。
6. 问题记录
仅列举遇到的费劲一点的问题。
- AsyncLogger错误 NoClassDefFoundError https://blog.csdn.net/besto229/article/details/123582303。
- log4j2 的xml配置有两种风格(concise and strict:简洁和严格)https://blog.csdn.net/besto229/article/details/123641670 。
- DefaultRolloverStrategy的Delete元素下面的条件要同时生效,需要IfAny(任何一个条件满足就行) 。
- 还有遇到很多问题,参考log4j2-spring.xml文件中的注释信息。
写在最后:
写了logback的demo再写的这个demo。 感觉要难好多,功能也要多不少,转来转去的看官方文档(English is a sad story)。 --------- 仅个人感受
- 照例,附上一张log4j2的类图。

- 感受一下 appender 类


本文介绍了一个SpringBoot项目如何使用Log4j2进行日志记录,包括配置控制台日志输出、文件日志输出及日志文件按日期和大小进行归档的方法。

被折叠的 条评论
为什么被折叠?



