使用Quartz工作线程拆分log4j输出

4
我正在开发一个基于Quartz的调度程序,其中包括使用CronTriggers运行的“CycledJob”。该应用程序的目的是根据来源国家处理来自不同电子邮件收件箱的输入。
根据输入的国家(例如美国、英国、法国等),应用程序触发一个作业线程来运行每个国家的处理周期,因此将有一个英国工作者线程、一个美国工作者线程、一个法国工作者线程等。在将输出格式化为log4j时,我使用线程参数,因此它会发出[ApplicationName_Worker-1]、[ApplicationName_Worker-2]等。尽管我可以扩展Quartz,但我想找到另一种解决方案,而不是干扰标准库。
问题在于:使用log4j时,我希望所有来自美国线程的日志项都输出到一个仅限美国的文件中,对于每个国家线程也是如此。我不介意它们留在一个统一的ConsoleAppender中,我想要的是FileAppender分割。我已经知道如何指定多个文件附加器等,我的问题是我无法根据国家区分它们。应用程序中有20多个类可以在执行链上使用,其中很少有我想要负担通过每个方法传递额外“上下文”参数的知识...我考虑了扩展log4j包装类的策略模式,但除非我可以让每个类知道它在哪个线程上以参数化日志记录器调用,否则似乎不可能。没有能够命名线程也会带来挑战(否则这将变得很容易!)。
因此,问题是:如何建议一种方法,使应用程序中的许多下级类,在处理输入时,每个不同的线程都知道它们处于特定国家线程的上下文中?
祝你好运理解,并请提出澄清问题!我希望有人能帮助我找到一个合适的方法来解决这个问题。欢迎所有建议。
4个回答

5
在每个国家的处理线程顶部,将国家代码放入Log4j的映射诊断上下文(MDC)中。这使用ThreadLocal变量,因此您不必明确地将国家传递上下调用堆栈。然后创建一个自定义过滤器,查看MDC,并过滤掉不包含当前附加器国家代码的任何事件。
在您的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);
  }
}
...

编写自定义过滤器

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> 

需要在你提供的基础上花费一点努力,但我最终还是按照你提出的方案得到了解决。非常感谢你的帮助! - agartzke

2
我希望我能比这更有帮助,但您可能想调查使用一些过滤器?也许您的日志记录可以输出国家代码,然后您可以基于此匹配您的过滤器?
一个StringMatchFilter应该能够为您匹配它。
无法将下面的地址正确地作为链接工作,但如果您查看它,它有一些关于使用过滤器进行单独文件记录的内容。 http://mail-archives.apache.org/mod_mbox/logging-log4j-user/200512.mbox/<1CC26C83B6E5AA49A9540FAC8D35158B01E2968E@pune.kaleconsultants.com >(只需在>之前删除空格)

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


1
为什么不在启动任务时调用 Thread.setName() 来设置线程名呢?如果出现访问问题,请配置 Quartz 使用您自己的线程池。

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=%m%n

  2. 为每个国家创建一个logger,并将它们分别定向到相应的appender上(以下为美国示例):

    log4j.logger.my-us-logger=debug,usfile

  3. 在您的代码中,根据正在处理的电子邮件所属的国家创建Logger:

    Logger logger = Logger.getLogger("my-us-logger");

  4. 确定如何为后续的方法调用执行步骤3。你可以在每个类/方法中重复步骤3;或者你可以修改方法签名来接受一个Logger作为输入;或者你可以可能使用ThreadLocal在方法间传递Logger。

额外信息:如果您不希望日志语句输出到父记录器(例如rootLogger),您可以将它们的可添加性标志设置为false(以美国为例)。
log4j.additivity.my-us-logger=false

你已经接近理解了。如果没有其他办法,我可能会采用这种方法,但是如果可以的话,我会尽量避免通过通用的方法/类传递特定于域的参数。目前无法根据线程确定您所在的国家。 - agartzke
我认为是log4j.additivity.my-us-logger=false,至少这是我们让它工作的方式,如果对其他人有用的话。 - FroMage
@FroMage - 谢谢...懒得复制/粘贴/编辑错误。已在答案中更正。 - jt.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接