我曾问过这个问题,以防止我需要跟踪 Logback 源代码,但由于最初的答案不充分,我最终还是不得不这样做。那么让我解释一下 SLF4J+Logback 系统的初始化以及它与上下文选择器的关系。
SLF4J 是一个日志 API,它允许各种实现,其中之一是 Logback。
当向org.slf4j.LoggerFactory.getLogger(...) 发出第一个请求时,SLF4J 框架通过创建org.slf4j.impl.StaticLoggerBinder 进行初始化。诀窍是StaticLoggerBinder 不随 SLF4J 分发;它实际上是在正在使用的任何日志实现中实现的(例如 Logback)。这是加载特定实现的一种有点麻烦的方法(服务加载器可能是更好的选择),但这有点离题了。
-
Logback 的StaticLoggerBinder 实现创建了一个单例ch.qos.logback.classic.util.ContextSelectorStaticBinder。这是设置上下文选择器的类。逻辑如下所述。
一个。如果“logback.ContextSelector”系统属性包含“JNDI”,请使用ContextJNDISelector。
b.如果"logback.ContextSelector" 系统属性包含其他任何内容,则假定该值是上下文选择器类的名称并尝试对其进行实例化。
c。否则,如果没有"logback.ContextSelector" 系统属性,请使用DefaultContextSelector。
1234563自动配置。 (更改默认配置策略是一个单独的主题,我不会在这里介绍。)
正如 Pieter 在另一个答案中指出的那样,安装自定义上下文选择器的方法是在 "logback.ContextSelector" 系统属性中提供实现类的名称。不幸的是,这种方法有点不稳定,显然必须在 1) 手动和 2) 进行任何 SLF4J 调用之前完成。 (这里再次使用服务加载器机制会更好;我已提交问题LOGBACK-1196 以进行此改进。)
如果您设法安装了自定义 Logback 上下文选择器,您可能希望将收到的 LoggerContext 存储在构造函数中,以便可以在 ContextSelector.getDefaultLoggerContext() 中返回它。除此之外,ContextSelector 中最重要的方法是ContextSelector.getLoggerContext();通过此方法,您将确定适合当前上下文的记录器上下文并返回它。
这里非常重要的LoggerContext 是ch.qos.logback.classic.LoggerContext,它实现了ILoggerFactory。当您访问主要的org.slf4j.LoggerFactory.getLogger(...) 方法时,它使用单例StaticLoggerBinder(上面讨论过)来查找记录器工厂。对于 Logback,StaticLoggerBinder 将使用单例 ContextSelectorStaticBinder(也在上面讨论过),它有望返回到您现在安装的自定义 LoggerContext。
(ContextSelector.getLoggerContext(String name)、ContextSelector.detachLoggerContext(String loggerContextName) 和 ContextSelector.getContextNames() 方法似乎只用于 JNDI 上下文选择器等您希望使用名称跟踪上下文选择器的情况。如果您不需要命名的上下文选择器,看来您可以安全地返回null 和适合这些方法的空列表。)
因此,自定义ContextSelector 需要简单地为调用线程提供一些适当配置的LoggerContext;这个LoggerContext 将用作ILoggerFactory,它基于LoggerContext 配置创建一个记录器。配置覆盖在Logback documentation;也许这里的讨论更清楚地说明了 Logback 记录器上下文的全部内容。
至于将LoggerContext 与线程相关联的实际机制,这对我来说从来都不是问题。很简单:我将使用我自己的Csar 库,它可以轻松处理此类事情。现在我已经弄清楚了如何挂钩到记录器上下文选择过程,我已经在一个名为 Clogr 的记录辅助库中实现了这一点,它使用了 Csar,并且我现在已经公开发布了。
因为我最终不得不自己进行所有研究,所以我会将自己的答案标记为已接受的答案,除非有人在我自己未涵盖的其他答案之一中指出重要的内容。