Logback上下文选择器的实际使用
Practical use of Logback context selectors
关于 Logback 的文档 logging separation indicates that I can use context selectors 在同一个 JVM 上创建不同的日志记录配置。不知何故,上下文选择器将允许我调用 LoggerFactory.getLogger(Foo.class)
,并且根据上下文,我将得到一个不同配置的记录器。
不幸的是,这些示例仅在专门配置的 Web 服务器(例如 Tomcat 或 Jetty)的上下文中处理 JNDI。我想知道我自己如何实际使用上下文选择器,例如在非 Web 应用程序中。
我的目标是在同一 JVM 上拥有多个日志记录配置。这是一种情况:
- 我希望一个线程使用类路径上的默认
logback.xml
配置获取记录器。
- 我希望另一个线程使用自定义目录中的另一个
logback.xml
获取记录器。
- 我想要第三个线程从编程配置中获取记录器。
我提供这些示例场景只是为了了解上下文选择器的实际使用——我将如何在现实生活中使用它们做一些有用的事情。
- 我如何使用上下文选择器来实现上述场景,以便
LoggerFactory.getLogger(Foo.class)
returns 一个基于线程的正确配置的记录器?
- 如果上下文选择器不能胜任这项任务,我如何手动获取一个
ILoggerFactory
实例,该实例会通过编程配置为我提供记录器?
虽然我熟悉log4j,但是我对slf4j和logback不是很熟悉。但是,阅读 logback 的文档后,我发现它与 log4j 有很多相似之处,所以我想我可以对您的问题提供一些见解。
编辑:
我正在更正我之前关于 logback 中的上下文选择器功能的陈述(请参阅下面删除的文本)。我太快得出了最初的结论,现在我相信实际上可以创建一个选择器来满足问题 #1 中的场景。选择器有点类似于 ContextJNDISelector
in that it would need to map threads to their corresponding configuration as well as their context instance somehow. Perhaps a simple solution would be provide a properties file that maps thread name or ID to the appropriate configuration file path, then have the selector read this file. The selector would, when asked for a context, look up the thread (either by name or ID) in this mapping and return the appropriate context object. The returned context may be an existing context that was already initialized previously or a new one that was just created for that thread by using the file path obtained from the properties read earlier. Based on the logback documentation 似乎选择器本身在应用程序的整个生命周期中都被重用 - 这意味着它在应用程序启动时一直使用到它退出。我认为是这种情况,因为选择器是通过使用 JVM 参数指定的:
You can specify a different context selector by setting the logback.ContextSelector system property. Suppose you would like to specify that context selector to an instance of the myPackage.myContextSelector class, you would add the following system property:
-Dlogback.ContextSelector=myPackage.myContextSelector
关于您的问题 #1,logback 似乎像 log4j 一样运行
它只需要一个配置文件。读取 logback
配置页我
看到它像 log4j 一样在类路径 上寻找这个文件 。
因此,您提供多种配置并不重要
文件,因为无论如何都只会加载一个 - 无论哪个
类加载器找到的第一个将被加载。所以你的问题是如何
您可以使用 ContextSelector
加载不同的 xml 配置文件
不同的线程真的无法回答,因为你需要
重写日志框架以添加这样的功能。这
ContextSelector
似乎打算与单独的
在同一个应用程序中没有单独线程的应用程序。
编辑:
我只是想补充一下,我在下面部分的评论是从实际使用logback的角度出发的。我提出这些替代方法是因为我相信它们更符合 logback 框架的预期用途。
#1 的替代方法:
现在,如果您想为不同的线程使用不同的记录器,这应该不会太难,因为您可以将 String
传递到 getLogger
方法调用中,例如 example in Chapter 9 of logback documentation :
Logger logger = LoggerFactory.getLogger("foo");
因此,如果您以有意义的方式命名每个线程,您可以为每个线程设置一个单独的记录器的配置文件,然后调用 getLogger
传递线程名称作为参数.但是,您会失去一些功能——例如分层记录器结构。
如果您希望每个线程有多个记录器,那么更好的方法是使用过滤器。您可以创建一个基于线程名称进行过滤的过滤器,然后配置 appender 以接受来自特定线程的消息。看看 filter page in the logback manual
至于你的第二个问题,关于 ILoggerFactory
returns 从程序配置记录器,我认为你最好的选择是修改 LoggerContext
类似于 this question
希望对您有所帮助!
基于对 logback 源代码的快速浏览,我认为您应该能够通过 java 系统 属性 插入您自己的 ContextSelector 来实现您提到的所有场景。我不确定是否记录了此功能,但它肯定存在:ContextSelector initialization
当 logback 自我引导时,它将首先创建默认的记录器上下文。默认记录器上下文通常通过 logback.xml 或 logback.groovy 配置。可以在此处找到有关配置默认记录器上下文的更多信息:Logback configuration
正如您已经阅读的那样,logback 具有上下文选择器的概念,它决定在创建记录器时使用哪个记录器上下文。默认上下文选择器只是 returns 默认记录器上下文,但通过插入您自己的上下文选择器,您几乎可以做任何您想做的事情。
以下示例向您展示了如何插入您自己的 ContextSelector。
选择器本身并没有做太多事情;实施它,以便满足您的需求取决于您 ;)
import ch.qos.logback.classic.ClassicConstants;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.selector.ContextSelector;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
public class LoggingExperiment {
public static void main(String[] args) {
System.getProperties().setProperty(
ClassicConstants.LOGBACK_CONTEXT_SELECTOR,
MyCustomContextSelector.class.getName()
);
LoggerFactory.getLogger(LoggingExperiment.class).info("test");
}
// this is implementation is just a copy of ch.qos.logback.classic.selector.DefaultContextSelector
// but it shows you how to bootstrap you're own context selector
public static class MyCustomContextSelector implements ContextSelector {
private LoggerContext defaultLoggerContext;
public MyCustomContextSelector(LoggerContext context) {
System.out.println("You're custom ContextSelector is being constructed!");
this.defaultLoggerContext = context;
}
public LoggerContext getDefaultLoggerContext() {
return defaultLoggerContext;
}
public LoggerContext getLoggerContext() {
//TODO create and return the LoggerContext that should be used
//if ("A".equals(Thread.currentThread().getName())){
// //return LoggerContext x and create it if necessary
// // Take a look at ch.qos.logback.classic.selector.ContextJNDISelector for an example of how to create & cache LoggerContexts.
// // Also note that when using multiple contexts you'll also have the adjust the other methods of this class appropriately.
//}else {
// return getDefaultLoggerContext();
//}
return getDefaultLoggerContext();
}
public LoggerContext detachLoggerContext(String loggerContextName) {
return defaultLoggerContext;
}
public List<String> getContextNames() {
return Arrays.asList(defaultLoggerContext.getName());
}
public LoggerContext getLoggerContext(String name) {
if (defaultLoggerContext.getName().equals(name)) {
return defaultLoggerContext;
} else {
return null;
}
}
}
}
我问这个问题是为了防止我需要跟踪 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
。这是设置上下文选择器的 class。逻辑如下所示。
一个。如果 "logback.ContextSelector" 系统 属性 包含 "JNDI",请使用 ContextJNDISelector
.
b。如果 "logback.ContextSelector"
系统 属性 包含任何其他内容,假设该值是上下文选择器的名称 class 并尝试实例化它。
c。否则如果没有"logback.ContextSelector"
系统属性,使用DefaultContextSelector
.
如果使用自定义 ContextSelector
,ContextSelectorStaticBinder
将使用以 LoggerContext
作为参数的构造函数实例化它,并将默认值传递给它LoggerContext
StaticLoggerBinder
已创建并自动配置。 (更改默认配置策略是一个单独的主题,我不会在这里介绍。)
正如 Pieter 在另一个答案中指出的那样,安装自定义上下文选择器的方法是在 "logback.ContextSelector"
系统 属性 中提供实现 class 的名称。不幸的是,这种方法有点不稳定,显然必须在进行任何 SLF4J 调用之前 1) 手动和 2) 完成。 (这里再一次服务加载器机制会好得多;我已经为这个改进提交了问题LOGBACK-1196。)
如果您设法安装自定义 Logback 上下文选择器,您可能希望将收到的 LoggerContext
存储在构造函数中,以便您可以 return 它在 ContextSelector.getDefaultLoggerContext()
.除此之外,ContextSelector
中最重要的方法是ContextSelector.getLoggerContext()
;通过此方法,您将确定适合当前上下文的记录器上下文和 return 它。
这里非常重要的LoggerContext
是ch.qos.logback.classic.LoggerContext
,它实现了ILoggerFactory
。当您访问 main org.slf4j.LoggerFactory.getLogger(...)
方法时,它使用单例 StaticLoggerBinder
(上面讨论过)来查找记录器工厂。对于 Logback StaticLoggerBinder
将使用单例 ContextSelectorStaticBinder
(也在上面讨论过),希望它 return 到您现在安装的自定义 LoggerContext
.
(ContextSelector.getLoggerContext(String name)
、ContextSelector.detachLoggerContext(String loggerContextName)
和 ContextSelector.getContextNames()
方法似乎只用于 JNDI 上下文选择器等您希望跟踪上下文选择器的情况使用名称。如果您不需要命名上下文选择器,看来您可以安全地 return null
和适合这些方法的空列表。)
因此自定义 ContextSelector
只需要提供一些 LoggerContext
为调用线程适当配置;此 LoggerContext
将用作 ILoggerFactory
,根据 LoggerContext
配置创建记录器。配置覆盖在Logback documentation;也许这里的讨论更清楚了 Logback 记录器上下文是什么。
至于将 LoggerContext
与线程相关联的实际机制,这对我来说从来都不是问题。很简单:我将使用我自己的 Csar library, which handles such things with ease. And now that I've figured out how to hook into the logger context selection process, I've already implemented this in a logging assistance library named Clogr,它使用 Csar,我现在已经公开发布了它。
因为我最终不得不自己做所有的研究,所以我会将我自己的答案标记为已接受的答案,除非有人在其他答案之一中指出了我自己没有涵盖的重要内容.
关于 Logback 的文档 logging separation indicates that I can use context selectors 在同一个 JVM 上创建不同的日志记录配置。不知何故,上下文选择器将允许我调用 LoggerFactory.getLogger(Foo.class)
,并且根据上下文,我将得到一个不同配置的记录器。
不幸的是,这些示例仅在专门配置的 Web 服务器(例如 Tomcat 或 Jetty)的上下文中处理 JNDI。我想知道我自己如何实际使用上下文选择器,例如在非 Web 应用程序中。
我的目标是在同一 JVM 上拥有多个日志记录配置。这是一种情况:
- 我希望一个线程使用类路径上的默认
logback.xml
配置获取记录器。 - 我希望另一个线程使用自定义目录中的另一个
logback.xml
获取记录器。 - 我想要第三个线程从编程配置中获取记录器。
我提供这些示例场景只是为了了解上下文选择器的实际使用——我将如何在现实生活中使用它们做一些有用的事情。
- 我如何使用上下文选择器来实现上述场景,以便
LoggerFactory.getLogger(Foo.class)
returns 一个基于线程的正确配置的记录器? - 如果上下文选择器不能胜任这项任务,我如何手动获取一个
ILoggerFactory
实例,该实例会通过编程配置为我提供记录器?
虽然我熟悉log4j,但是我对slf4j和logback不是很熟悉。但是,阅读 logback 的文档后,我发现它与 log4j 有很多相似之处,所以我想我可以对您的问题提供一些见解。
编辑:
我正在更正我之前关于 logback 中的上下文选择器功能的陈述(请参阅下面删除的文本)。我太快得出了最初的结论,现在我相信实际上可以创建一个选择器来满足问题 #1 中的场景。选择器有点类似于 ContextJNDISelector
in that it would need to map threads to their corresponding configuration as well as their context instance somehow. Perhaps a simple solution would be provide a properties file that maps thread name or ID to the appropriate configuration file path, then have the selector read this file. The selector would, when asked for a context, look up the thread (either by name or ID) in this mapping and return the appropriate context object. The returned context may be an existing context that was already initialized previously or a new one that was just created for that thread by using the file path obtained from the properties read earlier. Based on the logback documentation 似乎选择器本身在应用程序的整个生命周期中都被重用 - 这意味着它在应用程序启动时一直使用到它退出。我认为是这种情况,因为选择器是通过使用 JVM 参数指定的:
You can specify a different context selector by setting the logback.ContextSelector system property. Suppose you would like to specify that context selector to an instance of the myPackage.myContextSelector class, you would add the following system property:
-Dlogback.ContextSelector=myPackage.myContextSelector
关于您的问题 #1,logback 似乎像 log4j 一样运行
它只需要一个配置文件。读取 logback
配置页我
看到它像 log4j 一样在类路径 上寻找这个文件 。
因此,您提供多种配置并不重要
文件,因为无论如何都只会加载一个 - 无论哪个
类加载器找到的第一个将被加载。所以你的问题是如何
您可以使用 ContextSelector
加载不同的 xml 配置文件
不同的线程真的无法回答,因为你需要
重写日志框架以添加这样的功能。这
ContextSelector
似乎打算与单独的
在同一个应用程序中没有单独线程的应用程序。
编辑:
我只是想补充一下,我在下面部分的评论是从实际使用logback的角度出发的。我提出这些替代方法是因为我相信它们更符合 logback 框架的预期用途。
#1 的替代方法:
现在,如果您想为不同的线程使用不同的记录器,这应该不会太难,因为您可以将 String
传递到 getLogger
方法调用中,例如 example in Chapter 9 of logback documentation :
Logger logger = LoggerFactory.getLogger("foo");
因此,如果您以有意义的方式命名每个线程,您可以为每个线程设置一个单独的记录器的配置文件,然后调用 getLogger
传递线程名称作为参数.但是,您会失去一些功能——例如分层记录器结构。
如果您希望每个线程有多个记录器,那么更好的方法是使用过滤器。您可以创建一个基于线程名称进行过滤的过滤器,然后配置 appender 以接受来自特定线程的消息。看看 filter page in the logback manual
至于你的第二个问题,关于 ILoggerFactory
returns 从程序配置记录器,我认为你最好的选择是修改 LoggerContext
类似于 this question
希望对您有所帮助!
基于对 logback 源代码的快速浏览,我认为您应该能够通过 java 系统 属性 插入您自己的 ContextSelector 来实现您提到的所有场景。我不确定是否记录了此功能,但它肯定存在:ContextSelector initialization
当 logback 自我引导时,它将首先创建默认的记录器上下文。默认记录器上下文通常通过 logback.xml 或 logback.groovy 配置。可以在此处找到有关配置默认记录器上下文的更多信息:Logback configuration
正如您已经阅读的那样,logback 具有上下文选择器的概念,它决定在创建记录器时使用哪个记录器上下文。默认上下文选择器只是 returns 默认记录器上下文,但通过插入您自己的上下文选择器,您几乎可以做任何您想做的事情。
以下示例向您展示了如何插入您自己的 ContextSelector。 选择器本身并没有做太多事情;实施它,以便满足您的需求取决于您 ;)
import ch.qos.logback.classic.ClassicConstants;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.selector.ContextSelector;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
public class LoggingExperiment {
public static void main(String[] args) {
System.getProperties().setProperty(
ClassicConstants.LOGBACK_CONTEXT_SELECTOR,
MyCustomContextSelector.class.getName()
);
LoggerFactory.getLogger(LoggingExperiment.class).info("test");
}
// this is implementation is just a copy of ch.qos.logback.classic.selector.DefaultContextSelector
// but it shows you how to bootstrap you're own context selector
public static class MyCustomContextSelector implements ContextSelector {
private LoggerContext defaultLoggerContext;
public MyCustomContextSelector(LoggerContext context) {
System.out.println("You're custom ContextSelector is being constructed!");
this.defaultLoggerContext = context;
}
public LoggerContext getDefaultLoggerContext() {
return defaultLoggerContext;
}
public LoggerContext getLoggerContext() {
//TODO create and return the LoggerContext that should be used
//if ("A".equals(Thread.currentThread().getName())){
// //return LoggerContext x and create it if necessary
// // Take a look at ch.qos.logback.classic.selector.ContextJNDISelector for an example of how to create & cache LoggerContexts.
// // Also note that when using multiple contexts you'll also have the adjust the other methods of this class appropriately.
//}else {
// return getDefaultLoggerContext();
//}
return getDefaultLoggerContext();
}
public LoggerContext detachLoggerContext(String loggerContextName) {
return defaultLoggerContext;
}
public List<String> getContextNames() {
return Arrays.asList(defaultLoggerContext.getName());
}
public LoggerContext getLoggerContext(String name) {
if (defaultLoggerContext.getName().equals(name)) {
return defaultLoggerContext;
} else {
return null;
}
}
}
}
我问这个问题是为了防止我需要跟踪 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
。这是设置上下文选择器的 class。逻辑如下所示。一个。如果 "logback.ContextSelector" 系统 属性 包含 "JNDI",请使用
ContextJNDISelector
.b。如果
"logback.ContextSelector"
系统 属性 包含任何其他内容,假设该值是上下文选择器的名称 class 并尝试实例化它。c。否则如果没有
"logback.ContextSelector"
系统属性,使用DefaultContextSelector
.如果使用自定义
ContextSelector
,ContextSelectorStaticBinder
将使用以LoggerContext
作为参数的构造函数实例化它,并将默认值传递给它LoggerContext
StaticLoggerBinder
已创建并自动配置。 (更改默认配置策略是一个单独的主题,我不会在这里介绍。)
正如 Pieter 在另一个答案中指出的那样,安装自定义上下文选择器的方法是在 "logback.ContextSelector"
系统 属性 中提供实现 class 的名称。不幸的是,这种方法有点不稳定,显然必须在进行任何 SLF4J 调用之前 1) 手动和 2) 完成。 (这里再一次服务加载器机制会好得多;我已经为这个改进提交了问题LOGBACK-1196。)
如果您设法安装自定义 Logback 上下文选择器,您可能希望将收到的 LoggerContext
存储在构造函数中,以便您可以 return 它在 ContextSelector.getDefaultLoggerContext()
.除此之外,ContextSelector
中最重要的方法是ContextSelector.getLoggerContext()
;通过此方法,您将确定适合当前上下文的记录器上下文和 return 它。
这里非常重要的LoggerContext
是ch.qos.logback.classic.LoggerContext
,它实现了ILoggerFactory
。当您访问 main org.slf4j.LoggerFactory.getLogger(...)
方法时,它使用单例 StaticLoggerBinder
(上面讨论过)来查找记录器工厂。对于 Logback StaticLoggerBinder
将使用单例 ContextSelectorStaticBinder
(也在上面讨论过),希望它 return 到您现在安装的自定义 LoggerContext
.
(ContextSelector.getLoggerContext(String name)
、ContextSelector.detachLoggerContext(String loggerContextName)
和 ContextSelector.getContextNames()
方法似乎只用于 JNDI 上下文选择器等您希望跟踪上下文选择器的情况使用名称。如果您不需要命名上下文选择器,看来您可以安全地 return null
和适合这些方法的空列表。)
因此自定义 ContextSelector
只需要提供一些 LoggerContext
为调用线程适当配置;此 LoggerContext
将用作 ILoggerFactory
,根据 LoggerContext
配置创建记录器。配置覆盖在Logback documentation;也许这里的讨论更清楚了 Logback 记录器上下文是什么。
至于将 LoggerContext
与线程相关联的实际机制,这对我来说从来都不是问题。很简单:我将使用我自己的 Csar library, which handles such things with ease. And now that I've figured out how to hook into the logger context selection process, I've already implemented this in a logging assistance library named Clogr,它使用 Csar,我现在已经公开发布了它。
因为我最终不得不自己做所有的研究,所以我会将我自己的答案标记为已接受的答案,除非有人在其他答案之一中指出了我自己没有涵盖的重要内容.