当前位置: 首页 > news >正文

Spring Boot Logback:实现定时任务日志与业务日志隔离 - Higurashi

1. 问题背景

在 Spring Boot 应用中,我们通常使用@EnableScheduling启用定时任务。这些定时任务在执行过程中,可能会调用 Mapper 方法与数据库交互,产生大量的 SQL 日志。默认情况下,这些日志会与普通业务请求的日志一起输出到日志文件或控制台。

现在希望实现以下目标:

  1. 定时任务类及其调用链路中的所有日志(包括 SQL 日志),能够被单独输出到一个或多个指定文件。
  2. 普通业务调用 Mapper 产生的 SQL 日志,保持原有的输出位置不变(例如app.log)。
  3. 支持多个定时任务,每个任务的日志可以独立输出到不同的文件。
  4. 未设置特定日志标签的日志,应默认输出到app.log,不被定时任务日志分流机制影响。

2. 核心思路:MDC 与 Logback Filter

要实现日志的精准隔离,关键在于能够区分日志事件的来源。Logback 提供了 MDC (Mapped Diagnostic Context) 机制,允许我们在当前线程上下文中存储键值对信息。日志事件在被处理时,可以访问这些 MDC 信息,从而实现基于上下文的日志过滤和路由。

3. 实现步骤

3.1 添加 Janino 依赖

首先,在pom.xml中添加 Janino 依赖:

<!-- 用于 Logback EvaluatorFilter 中的表达式解析 -->
<dependency><groupId>org.codehaus.janino</groupId><artifactId>janino</artifactId><version>3.1.9</version> <!-- 请使用最新稳定版本 -->
</dependency>

Janino 是一个 Java 编译器库,后面会用来判断 MDC 上下文中的值,结合 EvaluatorFilter 实现基于 MDC 的日志过滤。

3.2 在每个定时任务入口设置唯一的logTag

为每个定时任务设置一个唯一的logTag,例如jobAjobB

import org.slf4j.MDC;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;@Component
public class MyJobs {private static final Logger logger = LoggerFactory.getLogger(MyJobs.class);@Scheduled(cron = "0 0/1 * * * ?") // 每分钟执行一次public void jobA() {MDC.put("logTag", "jobA"); // 设置任务 A 的 MDC 标记try {logger.info("执行任务 A...");// 假设这里调用了 Mapper 方法,SQL 日志会进入 jobA.log// userMapper.selectById(1);} finally {MDC.remove("logTag"); // 清理 MDC 标记}}@Scheduled(cron = "0 0/2 * * * ?") // 每两分钟执行一次public void jobB() {MDC.put("logTag", "jobB"); // 设置任务 B 的 MDC 标记try {logger.info("执行任务 B...");// 假设这里调用了 Mapper 方法,SQL 日志会进入 jobB.log// productMapper.selectByName("test");} finally {MDC.remove("logTag"); // 清理 MDC 标记}}// 假设这是一个普通的业务方法,不会设置 logTagpublic void normalBusinessMethod() {logger.info("执行普通业务方法...");}
}

3.3 配置logback-spring.xml使用SiftingAppender

这里需要注意MDCBasedDiscriminator必须设置defaultValue属性。我们将其设置为一个特殊值(如none),然后在sift内部的appender中添加一个EvaluatorFilter来过滤掉这个特殊值,这样在没有设置logTag时,不会输出到none.log中。

<configuration><!-- 普通业务日志 app.log --><appender name="APP" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/app.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder><!-- 如果有 logTag,不输出到 app.log --><filter class="ch.qos.logback.core.filter.EvaluatorFilter"><evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator"><expression>return mdc.get("logTag") != null;</expression></evaluator><OnMatch>DENY</OnMatch><OnMismatch>NEUTRAL</OnMismatch></filter></appender><!-- 定时任务日志,按 logTag 拆分 --><appender name="JOB_SIFT" class="ch.qos.logback.classic.sift.SiftingAppender"><discriminator class="ch.qos.logback.classic.sift.MDCBasedDiscriminator"><!-- 用 logTag 作为分片键 --><key>logTag</key><!-- 必须设置 defaultValue,但我们设置为 none,后续会过滤掉 --><defaultValue>none</defaultValue></discriminator><sift><!-- 动态创建的 Appender,名称和文件路径包含 logTag --><appender name="JOB-${logTag}" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/${logTag}.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>logs/${logTag}.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{logTag}] - %msg%n</pattern></encoder><!-- 过滤掉 defaultValue=none 的日志,避免生成 none.log --><filter class="ch.qos.logback.core.filter.EvaluatorFilter"><evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator"><expression>return mdc == null || mdc.get("jobTag") == null || "none".equals(mdc.get("jobTag"));</expression></evaluator><OnMatch>DENY</OnMatch> <!-- 匹配到空或为 none 的 logTag 则拒绝 --><OnMismatch>NEUTRAL</OnMismatch> <!-- 匹配到非 none 的 logTag 则中立,继续处理 --></filter></appender></sift></appender><!-- Root Logger 配置:所有日志都先经过这里,再由 Appender 的 Filter 进行分流 --><root level="INFO"><appender-ref ref="APP"/><appender-ref ref="JOB_SIFT"/></root></configuration>

效果

  • 普通业务日志(无logTag)将只写入app.log,不会生成none.log
  • 定时任务 A(logTag=jobA)的日志将只写入jobA.log
  • 定时任务 B(logTag=jobB)的日志将只写入jobB.log
  • 定时任务中调用的 Mapper 产生的 SQL 日志,也会跟随其所属任务的logTag写入对应的任务日志文件。

参考

Logback Manual - MDC
Logback Manual - SiftingAppender
Logback Manual - EvaluatorFilter
Logback 使用 SiftingAppender、MDC 实现日志文件分离,动态指定文件

http://www.hskmm.com/?act=detail&tid=23184

相关文章:

  • 网络流 最小割 Dinic算法
  • 15.VLANIF(2025年9月30日) - 教程
  • 树莓派搭建NAS之一:安装系统
  • 新手Markdown学习
  • 马云归来,“新零售”不死 - 指南
  • RNN
  • 10.2笔记
  • Shell / Bash 学习
  • 【Linux 架构探幽:从入门到内核・系统编程开篇】基础指令与权限精讲,筑牢框架制作根基
  • 使用 Dart 进行验证码识别
  • 用 Rust 进行验证码识别
  • teset3
  • Java并发编程(5)
  • 定时任务详解
  • 华为wlan无线配置 - 教程
  • PINN训练新思路:把初始条件和边界约束嵌入网络架构,解决多目标优化难题
  • 可持久化数据结构
  • 2025.10.2——1黄
  • 图的匹配
  • Tarjan 算法
  • Mondriaans Dream题解
  • 网络流 最大流 Dinic 算法
  • 2025.10.2 测试
  • 数学章节总结
  • 软件工程_作业一
  • CF VP 记录
  • LabVIEW与PLC 汽车驻车制动自动调整 - 实践
  • 04. 布局管理
  • 关于安装博客园皮肤中有关于配置音乐播放器的补充(awescnb)
  • AGC VP 记录 2