diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/turbo/GroupRoutingFilter.java b/logback-classic/src/main/java/ch/qos/logback/classic/turbo/GroupRoutingFilter.java new file mode 100644 index 0000000000..acc77f8dcf --- /dev/null +++ b/logback-classic/src/main/java/ch/qos/logback/classic/turbo/GroupRoutingFilter.java @@ -0,0 +1,73 @@ +package ch.qos.logback.classic.turbo; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.util.GroupContext; +import ch.qos.logback.core.spi.FilterReply; +import org.slf4j.MDC; +import org.slf4j.Marker; + +/** + * 自定义 TurboFilter:在日志事件创建前,将 GroupContext 中的 group 注入 MDC。 + * SiftingAppender 会根据 MDC 中的 LOG_GROUP 值路由到不同的日志文件。 + * @author YoranYe + */ +public class GroupRoutingFilter extends TurboFilter { + + private String mdcKey = GroupContext.getMdcKey(); // 默认 "LOG_GROUP" + private String defaultGroup = "default"; + + @Override + public FilterReply decide(Marker marker, Logger logger, Level level, + String format, Object[] params, Throwable t) { + if (!isStarted()) { + return FilterReply.NEUTRAL; + } + + // 优先从 GroupContext(ThreadLocal)获取 group + String group = GroupContext.getGroup(); + + // 如果 GroupContext 中没有,则检查 MDC 中是否已有(兼容手动 MDC.put 的场景) + if (group == null || group.isEmpty()) { + group = MDC.get(mdcKey); + } + + // 如果都没有,使用默认值 + if (group == null || group.isEmpty()) { + group = defaultGroup; + } + + // 注入 MDC,供 SiftingAppender 的 Discriminator 使用 + MDC.put(mdcKey, group); + + return FilterReply.NEUTRAL; + } + + @Override + public void start() { + super.start(); + } + + @Override + public void stop() { + super.stop(); + } + + // ---- getter/setter(支持在 logback.xml 中配置) ---- + + public String getMdcKey() { + return mdcKey; + } + + public void setMdcKey(String mdcKey) { + this.mdcKey = mdcKey; + } + + public String getDefaultGroup() { + return defaultGroup; + } + + public void setDefaultGroup(String defaultGroup) { + this.defaultGroup = defaultGroup; + } +} \ No newline at end of file diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/util/GroupContext.java b/logback-classic/src/main/java/ch/qos/logback/classic/util/GroupContext.java new file mode 100644 index 0000000000..fbeee07cca --- /dev/null +++ b/logback-classic/src/main/java/ch/qos/logback/classic/util/GroupContext.java @@ -0,0 +1,40 @@ +package ch.qos.logback.classic.util; + +/** + * 基于 ThreadLocal 的日志分组上下文。 + * 用于在调用日志前设置 group,TurboFilter 会自动读取并注入 MDC。 + * @author YoranYe + */ +public class GroupContext { + + private static final String MDC_KEY = "LOG_GROUP"; + private static final ThreadLocal GROUP_HOLDER = new ThreadLocal(); + + /** + * 设置当前线程的日志分组 + */ + public static void setGroup(String group) { + GROUP_HOLDER.set(group); + } + + /** + * 获取当前线程的日志分组 + */ + public static String getGroup() { + return GROUP_HOLDER.get(); + } + + /** + * 清除当前线程的日志分组(必须在 finally 中调用,防止内存泄漏) + */ + public static void clear() { + GROUP_HOLDER.remove(); + } + + /** + * 获取 MDC 中使用的 key + */ + public static String getMdcKey() { + return MDC_KEY; + } +} \ No newline at end of file diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/util/GroupLoggerFactory.java b/logback-classic/src/main/java/ch/qos/logback/classic/util/GroupLoggerFactory.java new file mode 100644 index 0000000000..2a103ba4d0 --- /dev/null +++ b/logback-classic/src/main/java/ch/qos/logback/classic/util/GroupLoggerFactory.java @@ -0,0 +1,62 @@ +package ch.qos.logback.classic.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 带分组功能的日志工厂。 + * 模拟 LoggerFactory.getLogger(Class, String group) 的效果。 + * + * 使用方式: + *
+ *   Logger logger = GroupLoggerFactory.getLogger(OrderService.class, "order");
+ *   try {
+ *       logger.info("处理订单...");
+ *   } finally {
+ *       GroupLoggerFactory.clearGroup();
+ *   }
+ * 
+ * + * @author YoranYe + */ +public class GroupLoggerFactory { + + /** + * 获取带分组的 Logger。 + * 日志将输出到 logs/{group}/ 目录下。 + * + * @param clazz 当前类 + * @param group 日志分组名称(如 "order", "user", "payment") + * @return SLF4J Logger 实例 + */ + public static Logger getLogger(Class clazz, String group) { + // 将 group 设置到 ThreadLocal 中,TurboFilter 会自动读取并注入 MDC + GroupContext.setGroup(group); + return LoggerFactory.getLogger(clazz); + } + + /** + * 获取带分组的 Logger(使用 logger name 而非 Class)。 + */ + public static Logger getLogger(String name, String group) { + GroupContext.setGroup(group); + return LoggerFactory.getLogger(name); + } + + /** + * 手动设置当前线程的日志分组。 + * 适用于 AOP、拦截器等场景。 + */ + public static void setGroup(String group) { + GroupContext.setGroup(group); + } + + /** + * 清除当前线程的日志分组。 + * 必须在 finally 块中调用,防止线程复用时 MDC 污染。 + */ + public static void clearGroup() { + GroupContext.clear(); + org.slf4j.MDC.remove(GroupContext.getMdcKey()); + } +} \ No newline at end of file