实际使用Logback上下文选择器

时间:2016-07-05 16:44:14

标签: java logging logback slf4j

Logback logging separation上的文档表明我可以使用context selectors在同一个JVM上创建不同的日志记录配置。不知何故,上下文选择器将允许我调用LoggerFactory.getLogger(Foo.class),并且根据上下文,我将获得一个不同配置的记录器。

不幸的是,这些示例仅在特殊配置的Web服务器(如Tomcat或Jetty)的上下文中处理JNDI。我想知道如何自己实际使用上下文选择器,例如在非Web应用程序中。

我的目标是在同一个JVM上拥有多个日志记录配置。这是一个场景:

  • 我想要一个线程使用类路径上的默认logback.xml配置来获取记录器。
  • 我想要另一个线程从自定义目录中使用另一个logback.xml来获取记录器。
  • 我想要第三个线程从编程配置中获取记录器。

我提供这些示例场景只是为了了解实际使用上下文选择器 - 我将如何在现实生活中对它们做一些有用的事情。

  1. 我如何使用上下文选择器来实现上述场景,以便LoggerFactory.getLogger(Foo.class)根据线程从正确的配置中返回记录器?
  2. 如果上下文选择器不能完成任务,我怎样才能手动获取一个ILoggerFactory实例,该实例可以从编程配置中获取记录器?

3 个答案:

答案 0 :(得分:2)

I had asked this question to prevent my needing to trace through the Logback source code, but as the initial answers were inadequate I wound up having to do so anyway. So let me explain the initialization of the SLF4J+Logback system and how it relates to context selectors.

SLF4J is a logging API that allows various implementations, one of them being Logback.

  1. When the first request is made to org.slf4j.LoggerFactory.getLogger(...), the SLF4J framework is initialized by creating a org.slf4j.impl.StaticLoggerBinder. The trick is that StaticLoggerBinder is not distributed with SLF4J; it is actually implemented in whichever logging implementation is being used (e.g. Logback). This is a somewhat cumbersome approach to loading a specific implementation (a service loader might have been a better choice), but that's somewhat beside the point here.

  2. Logback's implementation of StaticLoggerBinder creates a singleton ch.qos.logback.classic.util.ContextSelectorStaticBinder. This is the class that sets up the context selector. The logic goes something like is outlined below.

    a. If the "logback.ContextSelector" system property contains "JNDI", use a ContextJNDISelector.

    b. If the "logback.ContextSelector" system property contains anything else, assume the value is the name of a context selector class and try to instantiate that.

    c. Otherwise if there is no "logback.ContextSelector" system property, use a DefaultContextSelector.

  3. If a custom ContextSelector is used, ContextSelectorStaticBinder will instantiate it using a constructor that takes a LoggerContext as a parameter, and will pass it the default LoggerContext which StaticLoggerBinder has created and auto-configured. (Changing the default configuration strategy is a separate subject I won't cover here.)

As Pieter points out in another answer, the way to install a custom context selector is to provide the name of the implementing class in the "logback.ContextSelector" system property. Unfortunately this approach is a bit precarious, and it obviously has to be done 1) manually and 2) before any SLF4J calls are made. (Here again a service loader mechanism would be been much better; I have filed issue LOGBACK-1196 for this improvement.)

If you manage to get your custom Logback context selector installed, you'll probably want to store the LoggerContext you receive in the constructor so that you can return it in ContextSelector.getDefaultLoggerContext(). Other than this, the most important method in ContextSelector is ContextSelector.getLoggerContext(); from this method you will determine what logger context is appropriate for the current context and return it.

The LoggerContext which is so important here is ch.qos.logback.classic.LoggerContext, which implements ILoggerFactory. When you access the main org.slf4j.LoggerFactory.getLogger(...) method, it uses the singleton StaticLoggerBinder (discussed above) to look up the logger factory. For Logback StaticLoggerBinder will use the singleton ContextSelectorStaticBinder (also discussed above) which hopefully will return to your now installed custom LoggerContext.

(The ContextSelector.getLoggerContext(String name), ContextSelector.detachLoggerContext(String loggerContextName), and ContextSelector.getContextNames() methods seem to only be used in a situation such as the JNDI context selector in which you wish to keep track of context selectors using names. If you don't need named context selectors, it appears you can safely return null and an empty list as appropriate for these methods.)

Thus the custom ContextSelector need simply provide some LoggerContext appropriately configured for the calling thread; this LoggerContext will serve as the ILoggerFactory that creates a logger based upon the LoggerContext configuration. Configuration is covered in the Logback documentation; perhaps this discussion here makes clearer what the Logback logger context is all about.

As for the actual mechanics of associating a LoggerContext with a thread, that was never an issue for me. It's simple: I'll be using my own 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 which uses Csar and which I have now publicly released.

Because I wound up having to do all the research myself, I'll be marking my own answer as the accepted answer unless someone points out something important in one of the other answers which I didn't cover in my own.

答案 1 :(得分:1)

基于对logback源代码的快速浏览,我认为你应该能够通过java系统属性插入你自己的ContextSelector来实现你提到的所有场景。我不确定这个功能是否记录在案,但肯定存在:ContextSelector initialization

当logback bootstraps本身时,它将首先创建默认的记录器上下文。默认记录器上下文通常通过logback.xml或logback.groovy进行配置。有关配置默认记录器上下文的更多信息,请访问:Logback configuration

正如您已经读过的那样,logback具有上下文选择器的概念,用于决定在创建记录器时使用哪个记录器上下文。默认的上下文选择器只返回默认的记录器上下文,但是通过插入自己的上下文选择器,您可以做任何你想做的事情。

以下示例显示如何插入您自己的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;
        }
    }
}

}

答案 2 :(得分:0)

虽然我熟悉log4j,但我并不熟悉slf4j和logback。但是,阅读logback文档我发现log4j有很多相似之处,所以我想我可以提供一些你的问题的见解。

编辑:

我正在纠正我先前关于logback中的上下文选择器功能的陈述(参见下面的删除文本)。我太快地得出了我原来的结论,现在相信它实际上可以创建一个能够满足问题#1情景的选择器。选择器有点类似于ContextJNDISelector,因为它需要将线程映射到它们相应的配置以及它们的上下文实例。也许一个简单的解决方案是提供一个属性文件,将线程名称或ID映射到适当的配置文件路径,然后让选择器读取该文件。当被要求提供上下文时,选择器将在此映射中查找线程(通过名称或ID)并返回适当的上下文对象。返回的上下文可以是先前已初始化的现有上下文,也可以是通过使用从先前读取的属性获取的文件路径为该线程创建的新上下文。基于logback documentation,看起来选择器本身在应用程序的整个生命周期中都被重用 - 这意味着它在应用程序启动之前一直使用,直到它退出。我相信这是因为选择器是使用JVM参数指定的:

  

您可以通过设置logback.ContextSelector系统属性来指定其他上下文选择器。假设您要将该上下文选择器指定给myPackage.myContextSelector类的实例,您将添加以下系统属性:       -Dlogback.ContextSelector=myPackage.myContextSelector

关于你的问题#1,logback似乎像log4j一样运行 它只需要一个配置文件。阅读logback configuration page我 看到它在类路径上查找此文件就像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从程序化配置中返回记录器的第二个问题,我认为最好的办法是修改与this question中的答案类似的LoggerContext

希望这有帮助!