我正在开发一个应用程序,该应用程序由一个基于 Quartz 的整体调度程序和使用 CronTriggers 运行的“CycledJob”组成。该应用程序的目的是根据来源国家/地区处理来自不同电子邮件收件箱的输入。
根据它来自的国家(即美国、英国、法国等),应用程序会触发一个作业线程来运行每个国家的处理周期,因此会有一个英国工作线程,一个用于美国、法国等当将输出格式化为 log4j 时,我使用了 thread 参数,因此它会发出 [ApplicationName_Worker-1]、[ApplicationName_Worker-2] 等。尽我所能,我找不到方法命名线程,因为它们是从 Quartz 的线程池中拉出的。虽然我可能会扩展 Quartz,但我想制定一个不同的解决方案,而不是搞乱标准库。
问题是:使用 log4j 时,我希望将美国线程中的所有日志项输出到仅美国的文件中,对于每个国家/地区线程也是如此。我不在乎它们是否留在一个统一的 ConsoleAppender 中,FileAppender 拆分正是我在这里所追求的。我已经知道如何指定多个文件附加程序等,我的问题是我无法根据国家/地区进行区分。
应用程序中有 20 多个类可以位于执行链上,其中很少有我想承担通过每个方法传递额外“上下文”参数的知识的负担……我考虑过扩展策略模式log4j 包装器类,但除非我能让链中的每个类都知道它在哪个线程上参数化记录器调用,否则这似乎是不可能的。无法命名线程也会带来挑战(否则这会很容易!)。
所以问题是:建议采用什么方法来允许应用程序中的许多从属类(每个类用于每个不同的线程来处理输入)知道它们在记录日志时位于特定国家/地区线程的上下文中?
在每个国家/地区处理线程的顶部,将国家/地区代码放入 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>
我希望我能比这更有帮助,但您可能想使用一些过滤器进行调查?也许您的日志记录可以输出国家/地区代码,并且您可以根据该代码匹配您的过滤器?
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
为什么不在作业开始时调用 Thread.setName() 来设置线程的名称? 如果存在访问问题,请配置quartz使用自己的线程池。
我对你想要实现的目标的理解可能完全偏离,但我会尝试解决方案。听起来您想要为您正在处理电子邮件的每个国家/地区建立一个单独的日志文件。基于这种理解,这是一个可能的解决方案:
在 log4j 配置中为您希望单独登录的每个国家/地区设置一个附加程序(提供美国示例):
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
为每个国家/地区创建一个记录器,并将每个记录器定向到适当的附加程序(提供美国示例):
log4j.logger.my-us-logger=调试,usfile
在您的代码中,根据处理电子邮件的国家/地区创建记录器:
Logger logger = Logger.getLogger("my-us-logger");
确定如何完成后续方法调用的步骤 3。您可以在每个类/方法中重复步骤 3;或者您可以修改方法签名以接受记录器作为输入;或者您可以使用 ThreadLocal 在方法之间传递 Logger。
额外信息:如果您不希望日志语句发送到父记录器(例如 rootLogger),您可以将其可加性标志设置为 false(提供美国示例):
log4j.additivity.my-us-logger=false