【问题标题】:Spring: Different logging behaviour for different ApplicationContextSpring:不同ApplicationContext的不同日志记录行为
【发布时间】:2010-12-13 14:35:45
【问题描述】:

我正在开发一个使用 Spring 和 slf4j 的应用程序。该应用程序并行使用更多 ApplicationContext。 有什么办法可以让这些不同的 ApplicationContexts 使用不同的日志记录属性? 所以第一个 AC 可以登录到“x.txt”,而第二个 AC 可以登录到“y.txt”。

我不想使用更多的属性文件。适当的方法是在 Spring XML 配置文件中定义一个 Logger Bean,我可以在其中为相应的属性设置不同的输出目标。

例如:

<bean id="LoggerBean" class="???">
     <property name="target" value="${target}" />
</bean>

在这里我可以从源代码中操作目标变量,这将非常方便。

private static final Logger log = LoggerFactory.getLogger(MyClass.class);

所以LoggerFactory.getLogger 将使用LoggerBean bean 配置来实例化一个 Logger 类。

我需要一种方法,其中每个 ApplicationContext 都有一个自己的 LoggerFactory 对象,该对象具有不同的属性(例如不同的目标输出)。所以我不必重写当前的代码。

我使用由同一个 xml 配置文件配置的ApplicationContexts。所以这些 ApplicationContexts 使用 相同的课程。因此,所有Logger 都是从LoggerFactory 实例化的,它们使用的类名相同。 所有 Logger 都由LoggerFactory.getLogger(MyClass.class) 形式实例化,因为这些类在所有ApplicationContext ("MyClass") 中都是相同的,我无法定义不同的名称Loggers

感谢您的回复。

【问题讨论】:

    标签: spring logging javabeans


    【解决方案1】:

    您可以定义一个 Spring 管理的 bean 来配置记录器。例如,假设你使用 logback 来实现 slf4j API,这个类会在 Spring 设置好属性后,将指定的日志配置文件加载到 logback 中:

    public class LogBackConfigurer implements InitializingBean {
        private Resource location;
    
        public void setLocation(Resource location) {
            this.location = location;
        }
    
        public void afterPropertiesSet() throws Exception {
            JoranConfigurator configurator = new JoranConfigurator();
            LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
            configurator.setContext(loggerContext);
            configurator.doConfigure(location.getInputStream());
        }
    }
    

    在您希望拥有不同日志配置的每个 Spring 配置文件中,使用不同的日志配置文件位置定义如下所示的 bean。

    <bean class="com.example.log.LogBackConfigurer">
      <property name="location" value="classpath:a-logback.xml"/>
    </bean>
    

    该类修改单个应用程序范围的日志记录上下文,这是必要的,因为您想在应用程序代码中调用静态 Logger 工厂方法。为确保日志记录配置文件不会相互影响,它们必须分别定义不同名称的记录器。

    【讨论】:

    • 感谢您的回复。这对我来说是非常有用的。但是还有一些额外的问题。我使用由同一个 xml 配置文件配置的 ApplicationContexts。所以这些 ApplicationContexts 使用相同的类。正因为如此,所有 Logger 都从 LoggerFactory 实例化,并使用它们使用的相同类名。所有 Logger 都通过“LoggerFactory.getLogger(MyClass.class)”形式实例化,因为这些类在所有 ApplicationContext (“MyClass”) 中都是相同的,我无法定义不同名称的记录器。
    【解决方案2】:

    最终的解决方案如下:

    SLF4j 和 Logback 支持MDC,其中包含基于每个线程的键/值对。虽然我们的问题的主要优点是子线程自动继承其父线程的键/值对,但如果在 ApplicationContext 初始化期间创建新线程,该线程将从调用线程继承这些对。之后,您可以将这些存储的值包含在日志消息模式中。 所以我在加载ApplicationContext之前在MDC中放了一个特殊的ApplicationContext标识符。当使用 Logger 字段实例化类时,这些字段会获得它们的唯一标识符,该标识符包含在日志消息模式中。

    <Pattern>[%X{contextID}] - [%thread] - %date{dd/MM/yyyy HH:mm:ss} %level %msg%n</Pattern>
    

    LoggerSeparator.java

    public class LoggerSeparator implements InitializingBean{
        private Integer contextID;
    
        public LoggerSeparator() {}
    
        public void setContextID(int id) {
            this.contextID = id;
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            if ( contextID != null )
                MDC.put("contextID", contextID.toString());
        }
    }
    

    这个 bean 是 main.xml 中第一个定义的 Spring Bean。

    <bean class="com.myproblem.LoggerSeparator">
        <property name="contextID" value="${contextID}" />
    </bean>
    ...
    

    该类在 MD 中设置 contextID。 contextID 来自源代码。

    ...
    Properties props = new Properties();
    props.put("contextID", contextID);
    PropertyPlaceholderConfigurer conf = new PropertyPlaceholderConfigurer();
    conf.setProperties(props);
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
    context.addBeanFactoryPostProcessor(conf);
    context.setConfigLocation("beans/main.xml");
    context.refresh();
    ...
    

    日志消息被记录到一个文件中,但现在我可以通过它们的唯一标识符将它们分开。

    【讨论】:

      【解决方案3】:

      您可以使用自定义FactoryBean 将记录器添加到上下文中:

      public class Slf4jLoggerFactoryBean implements FactoryBean<Logger> {
      
          private String loggerName;
      
          public Logger getObject() throws Exception {
              return LoggerFactory.getLogger(loggerName);
          }
      
          public Class<?> getObjectType() {
              return Logger.class;
          }
      
          public boolean isSingleton() {
              return true;
          }
      
          public void setLoggerName(String loggerName) {
              this.loggerName = loggerName;
          }
      
      }
      

      然后 XML 将如下所示:

      <bean id="LoggerBean" class="com.example.Slf4jLoggerFactoryBean">
           <property name="target" value="${target}" />
      </bean>
      

      【讨论】:

      • private static final Logger log = LoggerFactory.getLogger(MyClass.class);这里 org.slf4j.LoggerFactory 的静态方法生成了一个 org.slf4j.Logger 类。我需要一种方法,其中每个 ApplicationContext 都有自己的 LoggerFactory 对象,该对象具有不同的属性(例如不同的目标输出)。所以我不必重写当前代码,只需将导入语句交换到我的 LoggerFactory 实现即可。
      • 更改了 FactoryBean 以返回 SLF4J 记录器
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-26
      • 1970-01-01
      • 2010-10-29
      • 1970-01-01
      • 2012-02-08
      • 2021-11-19
      相关资源
      最近更新 更多