使用Quartz Worker线程拆分log4j输出

时间:2008-09-27 03:02:57

标签: java multithreading log4j quartz-scheduler scheduler

我正在开发一个应用程序,它包含一个基于Quartz的整体调度程序和使用CronTriggers运行的“CycledJob”。该应用程序的目的是根据源国家处理来自不同电子邮件收件箱的输入。

根据它所来自的国家(即美国,英国,法国等),该应用程序触发一个作业线程来运行每个国家的处理周期,因此将有一个英国工人线程,一个用于美国,法国当将输出格式化为log4j时,我正在使用线程参数,因此它会发出[ApplicationName_Worker-1],[ApplicationName_Worker-2]等。尽我所能,我找不到一种方法来命名线程因为他们被拉出了Quartz的Thread Pools。虽然我可能会扩展Quartz,但我想制定一个不同的解决方案,而不是弄乱标准库。

问题在于:当使用log4j时,我希望将来自US线程的所有日志项输出到仅限US的文件,同样适用于每个国家/地区线程。我不在乎他们是否留在一个统一的ConsoleAppender中,FileAppender拆分就是我在这里所说的。我已经知道如何指定多个文件追加器等等,我的问题是我无法根据国家区分。应用程序中有20多个类可以在执行链上,我想通过每个方法传递一个额外的“上下文”参数的知识很少...我已经考虑过扩展一个策略模式log4j包装类,但除非我能让链中的每个类知道它所在的哪个线程参数化记录器调用,这似乎是不可能的。无法命名线程也会产生挑战(否则这很容易!)。

所以这里有一个问题:在一个应用程序中允许许多从属类的建议方法是什么,每个用于每个不同的线程来处理输入,知道它们在记录时都在特定国家线程的上下文中?

祝你好运,请提出澄清问题!我希望有人能够帮助我找到解决这个问题的好方法。欢迎所有建议。

4 个答案:

答案 0 :(得分:5)

在每个国家/地区的处理线程的顶部,将国家/地区代码放入Log4j的映射诊断上下文(MDC)。这使用ThreadLocal变量,这样您就不必显式地在调用堆栈中上下传递国家/地区。然后创建一个查看MDC的自定义过滤器,并过滤掉任何不包含当前appender国家/地区代码的事件。

Job

...
public static final String MDC_COUNTRY = "com.y.foo.Country";
public void execute(JobExecutionContext context)
  /* Just guessing that you have the country in your JobContext. */
  MDC.put(MDC_COUNTRY, context.get(MDC_COUNTRY));
  try {
    /* Perform your job here. */
    ...
  } finally {
    MDC.remove(MDC_COUNTRY);
  }
}
...

撰写自定义Filter

package com.y.log4j;

import org.apache.log4j.spi.LoggingEvent;

/**
 * This is a general purpose filter. If its "value" property is null, 
 * it requires only that the specified key be set in the MDC. If its 
 * value is not null, it further requires that the value in the MDC 
 * is equal.
 */
public final class ContextFilter extends org.apache.log4j.spi.Filter {

  public int decide(LoggingEvent event) {
    Object ctx = event.getMDC(key);
    if (value == null)
      return (ctx != null) ? NEUTRAL : DENY;
    else
      return value.equals(ctx) ? NEUTRAL : DENY;
  }

  private String key;
  private String value;

  public void setContextKey(String key) { this.key = key; }
  public String getContextKey() { return key; }
  public void setValue(String value) { this.value = value; }
  public String getValue() { return value; }

}

在你的log4j.xml中:

<appender name="fr" class="org.apache.log4j.FileAppender">
  <param name="file" value="france.log"/>
  ...
  <filter class="com.y.log4j.ContextFilter">
    <param name="key" value="com.y.foo.Country" />
    <param name="value" value="fr" />
  </filter>
</appender> 

答案 1 :(得分:2)

我希望我能比这更有帮助,但你可能想调查使用一些过滤器?也许您的日志记录可以输出国家/地区代码,您可以根据它来匹配您的过滤器?

StringMatchFilter应该可以为你匹配。

无法让以下地址作为链接正常工作,但是如果你看一下,它就会在使用过滤器的单独文件记录中有一些东西。

http://mail-archives.apache.org/mod_mbox/logging-log4j-user/200512.mbox/&lt; 1CC26C83B6E5AA49A9540FAC8D35158B01E2968E@pune.kaleconsultants.com> (只需删除&gt;之前的空格)

http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/spi/Filter.html

答案 2 :(得分:1)

我可能完全不基于我对你想要完成的事情的理解,但我会采取一种解决方案。对于您正在处理电子邮件的每个国家/地区而言,您需要一个单独的日志文件。基于这种理解,这是一个可能的解决方案:

  1. 在log4j配置中为您希望单独记录的每个国家/地区设置一个appender(提供美国示例):

    log4j.appender.usfile = org.apache.log4j.FileAppender

    log4j.appender.usfile.File = us.log

    log4j.appender.usfile.layout = org.apache.log4j.PatternLayout

    log4j.appender.usfile.layout.ConversionPattern =%米%N

  2. 为每个国家/地区创建一个记录器,并将每个国家/地区指向适当的appender(提供美国示例):

    log4j.logger.my-US-记录器=调试,usfile

  3. 在您的代码中,根据正在处理电子邮件的国家/地区创建您的记录器:

    Logger logger = Logger.getLogger(“my-us-logger”);

  4. 确定如何完成后续方法调用的第3步。您可以在每个类/方法中重复步骤3;或者您可以修改方法签名以接受Logger作为输入;或者您可以使用ThreadLocal在方法之间传递记录器。

  5. 额外信息:如果您不希望日志语句转到父记录器(例如rootLogger),您可以将其可加性标志设置为false(提供美国示例):

    log4j.additivity.my-us-logger=false
    

答案 3 :(得分:1)

为什么不在作业开始设置Thread的名称时调用Thread.setName()?如果存在访问问题,请将quartz配置为使用您自己的线程池。