【问题标题】:Practical use of Logback context selectorsLogback 上下文选择器的实际使用
【发布时间】:2016-07-05 16:44:14
【问题描述】:

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 实例,它可以通过编程配置为我提供记录器?

【问题讨论】:

    标签: java logging logback slf4j


    【解决方案1】:

    我曾问过这个问题,以防止我需要跟踪 Logback 源代码,但由于最初的答案不充分,我最终还是不得不这样做。那么让我解释一下 SLF4J+Logback 系统的初始化以及它与上下文选择器的关系。

    SLF4J 是一个日志 API,它允许各种实现,其中之一是 Logback。

    1. 当向org.slf4j.LoggerFactory.getLogger(...) 发出第一个请求时,SLF4J 框架通过创建org.slf4j.impl.StaticLoggerBinder 进行初始化。诀窍是StaticLoggerBinder 不随 SLF4J 分发;它实际上是在正在使用的任何日志实现中实现的(例如 Logback)。这是加载特定实现的一种有点麻烦的方法(服务加载器可能是更好的选择),但这有点离题了。

    2. Logback 的StaticLoggerBinder 实现创建了一个单例ch.qos.logback.classic.util.ContextSelectorStaticBinder。这是设置上下文选择器的类。逻辑如下所述。

      一个。如果“logback.ContextSelector”系统属性包含“JNDI”,请使用ContextJNDISelector

      b.如果"logback.ContextSelector" 系统属性包含其他任何内容,则假定该值是上下文选择器类的名称并尝试对其进行实例化。

      c。否则,如果没有"logback.ContextSelector" 系统属性,请使用DefaultContextSelector

    3. 1234563自动配置。 (更改默认配置策略是一个单独的主题,我不会在这里介绍。)

    正如 Pieter 在另一个答案中指出的那样,安装自定义上下文选择器的方法是在 "logback.ContextSelector" 系统属性中提供实现类的名称。不幸的是,这种方法有点不稳定,显然必须在 1) 手动和 2) 进行任何 SLF4J 调用之前完成。 (这里再次使用服务加载器机制会更好;我已提交问题LOGBACK-1196 以进行此改进。)

    如果您设法安装了自定义 Logback 上下文选择器,您可能希望将收到的 LoggerContext 存储在构造函数中,以便可以在 ContextSelector.getDefaultLoggerContext() 中返回它。除此之外,ContextSelector 中最重要的方法是ContextSelector.getLoggerContext();通过此方法,您将确定适合当前上下文的记录器上下文并返回它。

    这里非常重要的LoggerContextch.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,并且我现在已经公开发布了。

    因为我最终不得不自己进行所有研究,所以我会将自己的答案标记为已接受的答案,除非有人在我自己未涵盖的其他答案之一中指出重要的内容。

    【讨论】:

      【解决方案2】:

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

      当 logback 引导自身时,它会首先创建默认的记录器上下文。默认记录器上下文通常通过 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;
              }
          }
      }
      

      }

      【讨论】:

        【解决方案3】:

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

        编辑:

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

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

        关于您的问题 #1,logback 似乎像 log4j in 它只需要一个配置文件。阅读logback configuration page我 看到它像 log4j 一样在 类路径 上查找此文件。 因此,您提供多个配置并不重要 文件,因为无论如何只会加载一个 - 无论哪个 类加载器首先找到的将被加载。所以你的问题是如何 您可以使用 ContextSelector 加载不同的 xml 配置文件 不同的线程真的无法回答,因为你需要 重写日志框架以添加这样的功能。这 ContextSelector 似乎打算与单独的 同一应用程序中没有单独线程的应用程序。

        编辑:

        我只想补充一点,我在下面的部分中的 cmets 是从 logback 的实际使用角度出发的。我建议使用这些替代方法,因为我相信它们更符合 logback 框架的预期用途。

        #1 的替代方法:


        现在,如果您想为不同的线程使用不同的记录器,这应该不会太难,因为您可以将String 传递给getLogger 方法调用,如example in Chapter 9 of logback documentation

        Logger logger = LoggerFactory.getLogger("foo");

        因此,如果您以有意义的方式命名每个线程,您可以为每个线程设置一个单独的记录器的配置文件,然后调用getLogger 传递线程名称作为参数。但是,您会失去一些功能 - 例如分层记录器结构。

        如果您希望每个线程不止一个记录器,那么更好的方法是使用过滤器。您可以创建一个基于线程名称过滤的过滤器,然后配置附加程序以接受来自特定线程的消息。看看filter page in the logback manual


        关于你的第二个问题,关于有一个从编程配置返回记录器的ILoggerFactory,我认为你最好的办法是修改LoggerContext,类似于this question 中的答案

        希望这会有所帮助!

        【讨论】:

        • 恐怕你错过了问题的重点。关键是让日志上下文由线程分隔并透明地检索——而不是手动传递不同的字符串。而你关于“你需要重写日志框架”的说法是错误的——根据一些标准选择配置实际上是ContextSelector的全部目的。
        • 是的,ContextSelector 的重点是根据标准选择配置,但我的意思是它在应用程序级别而不是线程级别选择配置。每个应用程序加载一次配置文件,这就是配置文件的处理方式。除非您有多个应用程序,否则您根本无法加载不同的应用程序,因为日志记录框架不是为此而设计的。至于由线程“透明地”分隔不同的上下文,这不是上下文的设计目的。上下文是在应用程序级别。
        • 虽然我同意 D.B.我不会用完全相同的方式来表达它。创建 Logger 时会调用 ContextSelector。然后该 Logger 与一个类相关联。之后,多个线程将使用 Logger。那时没有办法让一个类有多个基于线程的记录器,除非每个线程都有一个类的实例。在这种情况下,您可以实现一个 ContextSelector,它具有一些 Thread Id 到 ContextSelector 的映射,但我认为必须以这种方式编写您的应用程序会导致比它解决的问题更多。
        • 也许我没有说清楚。我可以弄清楚与线程的实际关联——我需要帮助的是上下文选择器在 Logback 架构的整个方案中是如何工作的。我希望有人向我解释如何创建自定义上下文选择器,Logback 组件调用哪个以及何时、生命周期等。上面的解释只不过是概括——与在我已经参考过的文档。但没关系——我自己已经跟踪过代码,现在我可能和任何人一样可以解释它。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-06
        • 1970-01-01
        • 1970-01-01
        • 2013-02-21
        • 1970-01-01
        • 1970-01-01
        • 2017-08-08
        相关资源
        最近更新 更多