springboot2 log4j2 如何动态记录日志,并将日志根据api接口路径,保存到对应路径的文件中?

作者站长头像
站长
· 阅读数 5

场景:1、api接口地址:/payPage/createOrder/addSavespringboot2 收到请求后,如何把日志保存到:jar包目录/logs/payPage/createOrder/addSave/YYYY-MM-dd.log文件中

2、api接口地址:/merchant/goodlist/getListByQuery?currentPage=1&size=10springboot2 收到请求后,如何把日志保存到: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]]

回复
1个回答
avatar
test
2024-07-08

问题分析

感谢邀请,我也是因为工作原因,好久没动代码了。。。不过这个问题我还是感兴趣的,虽然之前没有碰到过,不过题主你的思路我觉得是对的。

我理解你的需求应该是,根据接口url的路径来保存日志,日志分类与url的路径保持一致。比如有10个url,那理论来说就会根据请求与否动态创建10个日志与其分别对应。

而题主你直接使用RollingFile为啥不行呢?我认为log4j2的常用的Appender,比如RollingFileAppenderFileAppender等,这些都是属于一个萝卜一个坑,也就是配置一个,那最终日志文件也只有一类(不是文件个数,是类型)

而题主想要的应该是配置只有一两个,但是最终生成日志的类型应该动态生成多个。而题主你之前虽然有一个${sys:log4fFile}作为变量配置,看似"动态"了,不过这个只是动态配置,不是动态生成,也就是仅仅动态的生成了最终日志的文件名,一旦根据启动获取到变量值,那这个日志文件名就不变了。

个人思路

所以我的思路是换一个Appender,要不然是官网有提供类似功能的,要么就是自定义Appender

我本能的直接搜索log4j2 dynamic log file,结果第二个搜索结果,是log4j2官网的一个FAQ的提问How do I dynamically write to separate log files,也就是"我如何动态地写入独立的日志文件",这个跟我们想要的结果很像,所以赶紧点进去看一下answer image

可以看到官网是推荐用RoutingAppender,通过定义路由规则,然后运行时结合ThreadContext来设置路由值,让路由进指定的Appenderanswer image

官网下面有一个示例,贴出来

<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,也就是ThreadContextkeyROUTINGKEY的值来进行路由匹配(注:前两个Route是有key属性的,最后一个Route没有),3个路由规则分别是

  1. ROUTINGKEY的值为special,则按照下级的RollingFile来生成special-${ctx:ROUTINGKEY}.log日志(这里意思就是可以自己设定多个特定keyAppender,这跟普通的RollingFile类似,还是一个萝卜一个坑的形式)
  2. ROUTINGKEY的没有设定值,则按照下级的下级的RollingFile来生成default.log(这里就相当于路由值空值处理)
  3. 最后一个Route没有key,所以直接根据ROUTINGKEY的值动态创建Appender生成other-${ctx:ROUTINGKEY}.log日志,最后这一个应该就是我们想要的效果了

那赶紧的,直接copy过去应该就可以用了,所以我准备了两个api

@GetMapping("/test/hi")
@GetMapping("/demo/u")

然后用上题主的Log4j2Interceptor,里面只是额外再用了postHandle再把ROUTINGKEY的删掉,保证日志不会乱入answer image

最后再把copy过来的日志配置文件中的最后一个Route改成题主的想要的效果

fileName="logs/${ctx:ROUTINGKEY}/${date:yyyy-MM-dd}.log"

最后运行一下效果,很奈斯啊answer image

很开心完美解决,谢谢题主的问题分享~我们应该都有所收获~( =•ω•= )

回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容