Spring Boot 2.x 最佳实践之 Apache Log4j2 集成
这篇博文讲解Spring Boot 项目如何集成 Log4j 2.x
0x01 前言
Spring Boot 不仅支持默认的Logback 日志框架的集成,而且也支持Log4j 2.x 的支持。
Apache Log4j 2.x 官网宣称:
Apache Log4j 2是对Log4j的升级,它比其前身Log4j1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些固有问题。
简单来说就是 Log4J 2.x 比logback 要更好更强大
0x02 依赖说明
- 下面是log4j 2.x 的一些依赖说明
组件 | 描述 |
---|---|
log4j-api | 应用程序应使用和编码的接口 |
log4j-core | 标准实现,也称为Log4j 2 Core,包含Appender,Filters等。 |
log4j-iostreams | 用于处理旧API的额外类,这些API期望来自java.io 的类用于记录 |
log4j-taglib | 使用Log4j 2在JavaServer Pages™中实现无Java登录的标记库。 |
JSP Tag Library (TLD Doc) | Log4j 2 JSP标记库的特殊类Javadoc标记库文档。 |
- 兼容组件依赖库
组件 | 描述 |
---|---|
Commons Logging Bridge | 允许针对Apache Commons Logging API编写的应用程序使用Log4j 2进行日志记录的桥接器。Common Logging API+ Log4j 2 Core |
SLF4J Binding | 允许针对SLF4J API编写的应用程序使用Log4j 2进行日志记录的桥接器 SLF4j+Log4j2 Core |
Java Util Logging Adapter | 允许针对java.util.logging API编写的应用程序使用Log4j 2进行日志记录的桥梁。java.util.logging API+Log4j 2 Core |
Log4j 1.2 API Bridge | 允许针对Log4j 1.2.x API编写的应用程序使用Log4j 2进行日志记录的桥梁。Log4j 1.2.x API—>Log4j 2 |
Log4j 2 to SLF4J Adapter | 允许针对Log4j 2 API编写的应用程序使用SLF4J进行日志记录的适配器。 Log4j2 —>SLF4j |
Log4j JMX GUI | 基于Java Swing的客户端,用于远程查看状态记录器和编辑Log4j配置。 |
Log4j JPA | Apache Log4j Java Persistence API Appender |
0x03 添加Log4j 2.x 依赖
- 对于一个新的Spring Boot 项目,我们只需要添加如下依赖即可
<!--排除默认的logback日志-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
如果你正在寻找 Log4j 1.x如何升级到Log4J 2.x 的内容, 请参考我的另外一篇博文 关于Log4j 1.x 升级Log4j 2.x那些事
0x04 XML配置篇
4.1 配置文件加载顺序
- 1.查看系统属性log4j.configurationFile是否配置了,如果配置了使用
ConfigurationFactory
- 2.如果没有配置,那么查找log4j2-test.properties
- 3.如果也没找到,那么检查是否有log4j2-test.yaml或者log4j2-test.yml
- 4.如果也没找到,那么检查是否有log4j2-test.json或者log4j2-test.jsn
- 5.如果也没找到,那么检查是否有log4j2-test.xml
- 6.如果也没找到,那么检查是否有log4j2.properties
- 7.如果也没找到,那么检查是否有log4j2.yaml或log4j2.yml
- 8.如果也没找到,那么检查是否有log4j2.json或log4j2.jsn
- 9.如果也没找到,那么检查是否有log4j2.xml
- 10.如果也没知道,那么使用默认的DefaultConfiguration,只输出到控制台
4.2 log4j2.xml 经典配置
- log4j2.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--从版本2.9开始,出于安全原因,Log4j不处理XML文件中的DTD-->
<!--monitorInterval="30" 每隔30秒检查下当前配置文件是否有改动如果有重新自动加载,最小间隔为5秒。 -->
<!-- status可选值"trace", "debug", "info", "warn", "error" and "fatal" log4j进行故障排除使用-->
<!--是否严格检查log4j2.xml配置文件格式 strict="false"-->
<!--verbose="" 加载插件时启用诊断信息。-->
<configuration name="log4j2Config" monitorInterval="30" status="WARN" strict="false" >
<Properties>
<property name="appName" value="myApp"/>
<Property name="baseDir" value="/opt/applog/${appName}/log"></Property>
<Property name="logName" value="${baseDir}/${appName}.log"></Property>
<Property name="logArchive" value="${baseDir}/$${date:yyyy-MM-dd}/${appName}-archive-%d{yyyy-MM-dd}-%i.log.gz"/>
</Properties>
<Appenders>
<!-- 输出到控制台 -->
<Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
<PatternLayout charset="GB18030" pattern="%d{yyyy/MM/dd HH:mm:ss,SSS} %-5level %l %n%msg%n"/>
</Console>
<!-- 滚动输出到文件 -->
<RollingFile name="rollingFile"
append="true"
bufferedIO="true"
bufferSize="8192"
createOnDemand="false"
immediateFlush="true"
fileName="${logName}"
filePattern="${logArchive}"
ignoreExceptions="true"
>
<PatternLayout>
<charset>UTF-8</charset>
<Pattern>%d{yyyy/MM/dd HH:mm:ss,SSS} %-5level %l %n%msg%n</Pattern>
</PatternLayout>
<Policies>
<!--每次启动是否归档滚动 -->
<!--<OnStartupTriggeringPolicy minSize="1"/>-->
<!-- 滚动策略根据大小进行滚动 -->
<SizeBasedTriggeringPolicy size="1GB" />
<!-- 滚动策略根据日期进行滚动 每天压缩一次
maxRandomDelay表示随机延迟翻转的最大秒数。 默认情况下,该值为0表示没有延迟-->
<TimeBasedTriggeringPolicy interval="1" modulate="false" maxRandomDelay="0"/>
</Policies>
<!--最多保留100个归档-->
<DefaultRolloverStrategy max="20" min="1">
<!--删除策略 -->
<Delete basePath="${baseDir}" maxDepth="8">
<IfFileName glob="*/app-*.log.gz">
<!--会保留大小10G或者最近的10个文件-->
<IfLastModified age="30d">
<IfAny>
<IfAccumulatedFileSize exceeds="10GB" />
<IfAccumulatedFileCount exceeds="100" />
</IfAny>
</IfLastModified>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.xingyun" level="DEBUG" additivity="true"/>
<root level="ERROR">
<AppenderRef ref="STDOUT" />
<AppenderRef ref="rollingFile" />
</root>
</Loggers>
</configuration>
关于日志格式可参考 log4j2配置详解(节点和输出格式)
0x05 代码中自定义线程日志
5.1 Log4J内置标准日志级别
- Log4J内置标准日志级别如下:
Standard Level | intLevel |
---|---|
OFF | 0 |
FATAL | 100 |
ERROR | 200 |
WARN | 300 |
INFO | 400 |
DEBUG | 500 |
TRACE | 600 |
ALL | Integer.MAX_VALUE |
- 自定义日志级别
- Log4J 2 支持自定义日志级别,可以使用Level.forName() 定义不同的日志级别.
5.2 代码中自定义线程日志工具类
如果想在代码中自定义线程日志工具类,我们需要先来看下设计架构图
log4j 2设计架构图
在设计这个的时候,刚开始走了不少坑,主要原因在于这里有一个很大的误区:
- 当我们自定义线程日志的时候很容易不小心引错包,
- 我们很容看到有两个包,这俩有啥区别呢?
org.apache.logging.log4j.Logger
和org.apache.logging.log4j.core.Logger
- log4j1.x 升级到log4j 2.x之后发生了一个改变,那就是实现和接口分离。
org.apache.logging.log4j.Logger
是log4j2 API 日志门面,只是日志接口类,位于log4j-api.jar
中org.apache.logging.log4j.core.Logger
是Log4j2 日志接口实现类,位于log4j-core.jar
中
我们如果想要自定义一个线程日志类,注意事项:
- 使用日志实现类
org.apache.logging.log4j.core.Logger
这个类才有logger.addAppender(appender);
方法 - 设置继承为false
, logger.setAdditive(false);
这样设置可以保持独立和log4j.xml 配置互相不冲突
工具类源码封装如下:
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.layout.PatternLayout;
import java.io.File;
import java.nio.charset.Charset;
/**
* @author 星云
* @功能 线程日志工具类 使用Log4j2自定义线程日志类
* @date 9/17/2019 7:09 AM
*/
public class ThreadCustomLogger{
/**
* 配置方法
*日志输出文件夹
*/
private static String logBasePath="/opt/applog/myApp/log/customThread";
/**
* 日志文件后缀
*/
private static String logFileSuffix=".log";
/**
* 单个文件最大大小
*/
private static String maxFileSize="1GB";
/**
* 是否使用缓冲区
*/
private static Boolean bufferedIo=true;
/**
* 缓冲区大小
*/
private static Integer bufferSize=8192;
/**
* 是否继承Root节点配置
*/
private static Boolean additive=false;
不经常修改/
/**
* 滚动追加器名称
*/
private static final String name="customRollingFileAppenderBuilder";
/**
* 字符集
*/
private static final String charSet="UTF-8";
/**
* 日志文件中日志输出格式
*/
private static String logOutFileFormat="%d{yyyy/MM/dd HH:mm:ss,SSS} %X{path} %X{ip} %-5level %l %n%msg%n";
/**
* 滚动压缩后日志文件路径和名称
*/
private static String filePatternValue=logBasePath+File.separator+"customThread-archive-%d{yyyy-MM-dd}-%i.log.gz";
/**
* 是否使用日志追加模式
*/
private static final Boolean appendEnable=true;
/**
* 是否忽略异常继续写入
*/
private static final Boolean ignoreExceptions=true;
/**
* 是否立即写入
*/
private static final Boolean immediateFlush=true;
/**
* 默认为false,该appender按需创建文件,当日志事件通过所有的filters并且通过路由指向了该appender,该appender仅仅创建该文件
*/
private static final Boolean createOnDemand=false;
/**
* 获取日志上下文
*/
private static LoggerContext loggerContext= (LoggerContext) LogManager.getContext(false);
/**
* 获取日志配置文件实例
*/
private static Configuration loggerConfiguration=loggerContext.getConfiguration();
/**
* 日志输出文件名称
*/
private static StringBuffer loggerFullFileName=null;
/**
* 日志
*/
private static Logger logger=null;
/**
* 滚动日志追加Appender Builder 对象
*/
private static RollingFileAppender.Builder rollingFileAppenderBuilder= null;
/**
* 滚动日志追加Appender对象
*/
private static RollingFileAppender rollingFileAppender= null;
/**
* 设计思路
* 1.log4j2.xml 根节点是configuration
* 2.Appenders 可以多个追加方法,比如追加到控制台,追加到文件等
* 3.代码中获取上面的配置,然后动态修改追加到文件的Appender
* @param threadFolderName
* @param loggerName
* @return
*/
public static Logger getLogger(String threadFolderName, String loggerName) {
//返回实例
logger =(Logger)LogManager.getLogger(loggerName);
//日志分割文件夹路径
loggerFullFileName=new StringBuffer();
loggerFullFileName.append(logBasePath);
loggerFullFileName.append(File.separator);
loggerFullFileName.append(threadFolderName);
//设置文件名称
loggerFullFileName.append(File.separator);
//设置日志的名称
loggerFullFileName.append(loggerName);
//日志文件后缀类型 默认.log
loggerFullFileName.append(logFileSuffix);
//不需要重复创建公共的配置过程
if(null==rollingFileAppenderBuilder){
rollingFileAppenderBuilder=initCustomRollingFileAppender();
}
//添加Appender
rollingFileAppender=rollingFileAppenderBuilder.withFileName(loggerFullFileName.toString()).build();
rollingFileAppender.start();
//设置追加器
logger.addAppender(rollingFileAppender);
//是否继承自log4j2.xml中Root节点的配置
logger.setAdditive(additive);
//设置拦截的日志级别
logger.setLevel(Level.DEBUG);
return logger;
}
/**
* 初始化
* @return
*/
private static RollingFileAppender.Builder initCustomRollingFileAppender(){
//日志打印输出布局
Layout layout= PatternLayout.newBuilder()
//设置字符集
.withCharset(Charset.forName(charSet))
//加载配置
.withConfiguration(loggerConfiguration)
//输出布局
.withPattern(logOutFileFormat).build();
//设置默认值
DefaultRolloverStrategy defaultRolloverStrategy= DefaultRolloverStrategy.newBuilder()
//从2.8版本开始,如果fileIndex属性设置为nomax,那么最大和最小值,都将会被忽略掉
.withFileIndex("noMax")
//.withMin("1")
//.withMax("20")
//设置压缩级别0-9,其中0=无,1=最佳速度,通过9=最佳压缩。只适用于ZIP文件。
//.withCompressionLevelStr("9")
.withConfig(loggerConfiguration)
.build();
//根据大小触发滚动策略 当文件大小增加到1GB时候触发该策略
SizeBasedTriggeringPolicy sizeBasedTriggeringPolicy= SizeBasedTriggeringPolicy.createPolicy(maxFileSize==null?null:maxFileSize);
//根据日期和时间触发滚动策略
TimeBasedTriggeringPolicy timeBasedTriggeringPolicy= TimeBasedTriggeringPolicy.newBuilder()
//根据日期格式中最具体的时间单位来决定应该多久发生一次rollover
//例如,在日期模式中小时为具体的时间单位,那么每4小时会发生4次rollover,默认值为1
//如果是yyyy-MM-dd 那么1表示一天 如果是yyyy-MM 那么1表示一个月
.withInterval(1)
//表示是否调整时间间隔以使在时间间隔边界发生下一个rollover
//假设小时为具体的时间单元,当前时间为上午3点,时间间隔为4,第一次发送rollover是在上午4点,接下来是上午8点,接着是中午,接着是下午4点等发生
.withModulate(false)
//默认值 0 单位:秒,下一次触发时间会在interval基础上,增加一个随机的毫秒数Random.nextLong(0, 1+maxRandomDelay*1000)
.withMaxRandomDelay(0)
.build();
//日志滚动追加器
RollingFileAppender.Builder defaultConfigRollingFileAppenderBuilder = RollingFileAppender.newBuilder()
.withName(name)
//日志是否追加模式
.withAppend(appendEnable)
//是否使用缓冲区
.withBufferedIo(bufferedIo)
//缓冲区大小
.withBufferSize(bufferSize)
.withCreateOnDemand(createOnDemand)
//是否立即刷新
.withImmediateFlush(immediateFlush)
//log4j 2.9.1 以上版本使用.setIgnoreExceptions(true)替换
.withIgnoreExceptions(ignoreExceptions)
//日志输出格式 log4j 2.9.1 以上版本使用.setLayout(layout)替换
.withLayout(layout)
//滚动输出归档压缩包文件命名格式
.withFilePattern(filePatternValue)
//用于决定是否发生rollover的策略 根据大小和日志进行滚动
.withPolicy(CompositeTriggeringPolicy.createPolicy(sizeBasedTriggeringPolicy,timeBasedTriggeringPolicy))
//用于决定压缩文件的名称和路径 单个文件最大1G
.withStrategy(defaultRolloverStrategy);
return defaultConfigRollingFileAppenderBuilder;
}
}
- 关于这个,当然网上还有一些其他设计思路,但是感觉和我想要的有些不太一样,感觉怪怪的。
- 比如这篇:Log4j2代码方式配置实现线程级动态控制写的很不错
0x06 日志调用方法汇总
在代码中调用方式如下:
import com.xingyun.springbootwithlog4j2sample.util.ThreadCustomLogger;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 星云
* @功能
* @date 9/15/2019 3:45 PM
*/
@Slf4j
@RestController
public class LogController {
private static final org.slf4j.Logger LOGGER_SLF4J= LoggerFactory.getLogger(LogController.class);
private static final Logger LOGGER_LOG4J2= LogManager.getLogger(LogController.class);
private static Logger loggerCustom= null;
@GetMapping(value = "/log.do")
public String log(){
//第一种日志使用SLF4J 日志门面调用
LOGGER_SLF4J.debug("this is debug message with slf4j");
LOGGER_SLF4J.info("this is info message with slf4j");
LOGGER_SLF4J.warn("this is warn message with slf4j");
LOGGER_SLF4J.error("this is error message with slf4j");
//第二种方式使用log4j API 日志门面调用
LOGGER_LOG4J2.debug("this is debug message with log4j2");
LOGGER_LOG4J2.info("this is info message with log4j2");
LOGGER_LOG4J2.warn("this is warn message with log4j2");
LOGGER_LOG4J2.error("this is error message with log4j2");
//第三种方式 配合lombok @Slf4j注解使用
log.debug("this is debug message with lombok");
log.info("this is info message with lombok");
log.warn("this is warn message with debug");
log.error("this is error message with lombok");
//第四种方式自定义线程日志
if(null==loggerCustom){
loggerCustom= ThreadCustomLogger.getLogger("myThread",LogController.class.getSimpleName());
}
loggerCustom.debug("this is custom debug message");
loggerCustom.info("this is custom info message");
loggerCustom.warn("this is custom warn message");
loggerCustom.error("this is custom error message");
return "log test finished,please check console message";
}
}
0x07 源码下载
0x08 参考资料
本篇完,喜欢我的博文,欢迎点赞,关注 ~