springboot2 log4j2 如何动态记录日志,并将日志根据api接口路径,保存到对应路径的文件中?
场景:1、api接口地址:/payPage/createOrder/addSave
springboot2 收到请求后,如何把日志保存到:jar包目录/logs/payPage/createOrder/addSave/YYYY-MM-dd.log
文件中
2、api接口地址:/merchant/goodlist/getListByQuery?currentPage=1&size=10
springboot2 收到请求后,如何把日志保存到:jar包目录/logs/merchant/goodlist/getListByQuery/YYYY-MM-dd.log
文件中
我目前查到的方法有:
1、在程序main函数入口中,设置:System.setProperty("log4fFile", "runtimeTest.log");
2、在 log4j-spring.xml 中,设置文件名,加入变量:
<RollingFile name="RollingFile" fileName="${LOG_PATH}/${sys:log4fFile}/${date:yyyy-MM-dd}_${APP_NAME}.log"
filePattern="${LOG_BACKUP_PATH}/${date:yyyy-MM-dd}/${APP_NAME}-%d{yyyy-MM-dd}_%i.log.zip">
<!--输出日志的格式, 不设置默认为:%m%n-->
<PatternLayout pattern="${PATTERN_FILE}"/>
<!--只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<!--归档设置-->
<Policies>
<!--按时间间隔归档:
1. interval=时间间隔, 单位由filePattern的%d日期格式指定, 此处配置代表每一天归档一次
2. modulate="true" 是否对interval取模,决定了下一次触发的时间点
-->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<!-- 按照日志文件的大小: size表示当前日志文件的最大size,支持单位:KB/MB/GB-->
<SizeBasedTriggeringPolicy size="50MB"/>
</Policies>
<!-- 历史日志配置: 该属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
3、我使用了拦截器,并进行了注册
public class Log4j2Interceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String traceId = UUID.randomUUID().toString().replaceAll("-", "");
// org.slf4j.MDC
MDC.put("logid", traceId);// 用来给日志文件使用
// org.apache.logging.log4j.ThreadContext
ThreadContext.put("logid", traceId); // 经测试,这两行都可行。
System.setProperty("log4fFile", traceId);
return true;
}
}
注册
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration log4j2Registration = registry.addInterceptor(useLog4j2Inter());
log4j2Registration
.addPathPatterns("/**");
}
@Bean
public Log4j2Interceptor useLog4j2Inter() {
return new Log4j2Interceptor();
}
}
测试结果1:main函数入口 获取不到api接口的地址,程序启动后,接口请求不会访问到main函数入口。-->在主入口动态修改log4fFile失败。2、在拦截器中动态修改log4fFile失败。由此,因为无法动态log4fFile的值,由此失败。
2023年3月21日经过测试提供大佬提供的思路完美解决,运行中发现一个小的问题 百度后猜测:1、是启动时,初试值为空,但我在Log4j2Interceptor.preHandle设置后依然报错,2、文件夹没有权限,但我用的windows,应该没有权限问题。想请大佬帮我看看。
我在log4j-spring.xml中配置:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 扫描配置文件是否被改动过 monitorInterval=5秒 -->
<Configuration status="DEBUG" monitorInterval="5">
<Appenders>
<Routing name="Routing">
<Routes pattern="$${ctx:ROUTINGKEY}">
<Route>
<RollingFile name="Rolling-${ctx:ROUTINGKEY}" fileName="./logs/${ctx:ROUTINGKEY}/${date:yyyy-MM}.log"
filePattern="./logs/${date:yyyy-MM}/${ctx:ROUTINGKEY}-other-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout>
<pattern>%d{ISO8601} [%t] %p %c{3} - %m%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Routing"/>
</Root>
</Loggers>
</Configuration>
启动后会报错:2023-03-21 11:13:17,938 Thread-22 ERROR Could not create plugin of type class org.apache.logging.log4j.core.appender.RollingFileAppender for element RollingFile: java.lang.IllegalStateException: ManagerFactory [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$RollingFileManagerFactory@47981a5] unable to create manager for [./logs/${ctx:ROUTINGKEY}/2023-03.log] with data [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$FactoryData@30b5c0bd[pattern=./logs/2023-03/${ctx:ROUTINGKEY}-other-%d{yyyy-MM-dd}-%i.log.gz, append=true, bufferedIO=true, bufferSize=8192, policy=CompositeTriggeringPolicy(policies=[TimeBasedTriggeringPolicy(nextRolloverMillis=0, interval=6, modulate=true), SizeBasedTriggeringPolicy(size=10485760)]), strategy=DefaultRolloverStrategy(min=1, max=7, useMax=true), advertiseURI=null, layout=%d{ISO8601} [%t] %p %c{3} - %m%n, filePermissions=null, fileOwner=null]] java.lang.IllegalStateException: ManagerFactory [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$RollingFileManagerFactory@47981a5] unable to create manager for [./logs/${ctx:ROUTINGKEY}/2023-03.log] with data [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$FactoryData@30b5c0bd[pattern=./logs/2023-03/${ctx:ROUTINGKEY}-other-%d{yyyy-MM-dd}-%i.log.gz, append=true, bufferedIO=true, bufferSize=8192, policy=CompositeTriggeringPolicy(policies=[TimeBasedTriggeringPolicy(nextRolloverMillis=0, interval=6, modulate=true), SizeBasedTriggeringPolicy(size=10485760)]), strategy=DefaultRolloverStrategy(min=1, max=7, useMax=true), advertiseURI=null, layout=%d{ISO8601} [%t] %p %c{3} - %m%n, filePermissions=null, fileOwner=null]]
问题分析
感谢邀请,我也是因为工作原因,好久没动代码了。。。不过这个问题我还是感兴趣的,虽然之前没有碰到过,不过题主你的思路我觉得是对的。
我理解你的需求应该是,根据接口url
的路径来保存日志,日志分类与url
的路径保持一致。比如有10个url
,那理论来说就会根据请求与否动态创建10个日志与其分别对应。
而题主你直接使用RollingFile
为啥不行呢?我认为log4j2
的常用的Appender
,比如RollingFileAppender
、FileAppender
等,这些都是属于一个萝卜一个坑,也就是配置一个,那最终日志文件也只有一类(不是文件个数,是类型)
而题主想要的应该是配置只有一两个,但是最终生成日志的类型应该动态生成多个。而题主你之前虽然有一个${sys:log4fFile}
作为变量配置,看似"动态"了,不过这个只是动态配置,不是动态生成,也就是仅仅动态的生成了最终日志的文件名,一旦根据启动获取到变量值,那这个日志文件名就不变了。
个人思路
所以我的思路是换一个Appender
,要不然是官网有提供类似功能的,要么就是自定义Appender
。
我本能的直接搜索log4j2 dynamic log file
,结果第二个搜索结果,是log4j2
官网的一个FAQ的提问How do I dynamically write to separate log files
,也就是"我如何动态地写入独立的日志文件",这个跟我们想要的结果很像,所以赶紧点进去看一下
可以看到官网是推荐用RoutingAppender
,通过定义路由规则,然后运行时结合ThreadContext
来设置路由值,让路由进指定的Appender
。
官网下面有一个示例,贴出来
<Routing name="Routing">
<Routes pattern="$${ctx:ROUTINGKEY}">
<!-- This route is chosen if ThreadContext has value 'special' for key ROUTINGKEY. -->
<Route key="special">
<RollingFile name="Rolling-${ctx:ROUTINGKEY}" fileName="logs/special-${ctx:ROUTINGKEY}.log"
filePattern="./logs/${date:yyyy-MM}/${ctx:ROUTINGKEY}-special-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout>
<pattern>%d{ISO8601} [%t] %p %c{3} - %m%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="6" modulate="true" />
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
</RollingFile>
</Route>
<!-- This route is chosen if ThreadContext has no value for key ROUTINGKEY. -->
<Route key="$${ctx:ROUTINGKEY}">
<RollingFile name="Rolling-default" fileName="logs/default.log"
filePattern="./logs/${date:yyyy-MM}/default-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout>
<pattern>%d{ISO8601} [%t] %p %c{3} - %m%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="6" modulate="true" />
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
</RollingFile>
</Route>
<!-- This route is chosen if ThreadContext has a value for ROUTINGKEY
(other than the value 'special' which had its own route above).
The value dynamically determines the name of the log file. -->
<Route>
<RollingFile name="Rolling-${ctx:ROUTINGKEY}" fileName="logs/other-${ctx:ROUTINGKEY}.log"
filePattern="./logs/${date:yyyy-MM}/${ctx:ROUTINGKEY}-other-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout>
<pattern>%d{ISO8601} [%t] %p %c{3} - %m%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="6" modulate="true" />
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
从上面配置来看,Routes
下面配置了3个Route
,也就是3个路由规则,根据匹配模式是ctx:ROUTINGKEY
,也就是ThreadContext
中key
为ROUTINGKEY
的值来进行路由匹配(注:前两个Route
是有key
属性的,最后一个Route
没有),3个路由规则分别是
- 若
ROUTINGKEY
的值为special
,则按照下级的RollingFile
来生成special-${ctx:ROUTINGKEY}.log
日志(这里意思就是可以自己设定多个特定key
的Appender
,这跟普通的RollingFile
类似,还是一个萝卜一个坑的形式) - 若
ROUTINGKEY
的没有设定值,则按照下级的下级的RollingFile
来生成default.log
(这里就相当于路由值空值处理) - 最后一个
Route
没有key
,所以直接根据ROUTINGKEY
的值动态创建Appender
生成other-${ctx:ROUTINGKEY}.log
日志,最后这一个应该就是我们想要的效果了
那赶紧的,直接copy过去应该就可以用了,所以我准备了两个api
@GetMapping("/test/hi")
@GetMapping("/demo/u")
然后用上题主的Log4j2Interceptor
,里面只是额外再用了postHandle
再把ROUTINGKEY
的删掉,保证日志不会乱入
最后再把copy
过来的日志配置文件中的最后一个Route
改成题主的想要的效果
fileName="logs/${ctx:ROUTINGKEY}/${date:yyyy-MM-dd}.log"
最后运行一下效果,很奈斯啊
很开心完美解决,谢谢题主的问题分享~我们应该都有所收获~( =•ω•= )
- 经过验证的有效解决办法
- 自己的经验指引,对解决问题有帮助
- 遵循 Markdown 语法排版,代码语义正确
- 询问内容细节或回复楼层
- 与题目无关的内容
- “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容