4

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

根据它来自的国家(即美国、英国、法国等),应用程序会触发一个工作线程来运行每个国家的处理周期,因此会有一个英国工作线程,一个用于美国、法国等。将输出格式化为 log4j 时,我使用线程参数,因此它会发出 [ApplicationName_Worker-1]、[ApplicationName_Worker-2] 等。尽我所能,我找不到命名线程的方法,因为它们'重新从 Quartz 的线程池中退出。尽管我可能会扩展 Quartz,但我想制定一个不同的解决方案,而不是弄乱标准库。

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

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

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

4

4 回答 4

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);
  }
}
...

编写自定义过滤器

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> 
于 2008-09-27T18:21:14.677 回答
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

于 2008-09-27T03:46:52.797 回答
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. 为每个国家/地区创建一个记录器,并将它们中的每一个定向到适当的附加程序(提供美国示例):

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

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

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

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

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

log4j.additivity.my-us-logger=false
于 2008-09-27T04:48:59.497 回答
1

当您的工作开始设置 Thread 的名称时,为什么不直接调用 Thread.setName() 呢?如果出现访问问题,配置quartz使用自己的线程池。

于 2008-09-27T20:45:23.253 回答