Разделение выхода log4j кварцевыми рабочими нитями
Я работаю над приложением, которое состоит из общего планировщика на основе кварца и CycledJob, запущенного с использованием CronTriggers. Целью приложения является обработка входных данных из разных почтовых ящиков в зависимости от страны происхождения.
В зависимости от страны, из которой оно поступило (например, США, Великобритания, FR и т. Д.), Приложение запускает один поток задания для запуска цикла обработки каждой страны, поэтому будет рабочий поток в Великобритании, один для США, Франции и т. Д. При форматировании вывода в log4j я использую параметр потока, поэтому он выдает [ApplicationName_Worker-1], [ApplicationName_Worker-2] и т. Д. Попробуйте, как я мог бы, я не могу найти способ назвать потоки, так как они ' вытащил из Кварцевых Нитейных Бассейнов. Хотя я мог бы пойти дальше, чтобы расширить Quartz, я бы хотел разработать другое решение вместо того, чтобы возиться со стандартной библиотекой.
Вот проблема: при использовании log4j я хотел бы, чтобы все элементы журнала из потока в США выводились в файл только для США, аналогично для каждого из потоков в стране. Мне все равно, если они останутся в одном объединенном ConsoleAppender, разделение FileAppender - это то, что мне нужно. Я уже знаю, как указать несколько файловых приложений, и моя проблема в том, что я не могу различить в зависимости от страны. В приложении имеется более 20 классов, которые могут находиться в цепочке выполнения, очень немногие из которых я хочу обременять знаниями о передаче дополнительного параметра "context" через КАЖДЫЙ метод... Я рассмотрел шаблон Стратегии, расширяющий Класс-оболочка log4j, но если я не смогу сообщить каждому классу в цепочке, в каком потоке он параметризует вызов логгера, это кажется невозможным. Без возможности назвать поток также создает проблему (иначе это было бы легко!).
Итак, вот вопрос: Какой был бы предложенный подход, чтобы позволить многим подчиненным классам в приложении, каждый из которых используется для каждого отдельного потока, обрабатывать входные данные, зная, что они находятся в контексте определенного потока страны, когда они регистрируются?
Удачи в понимании, и, пожалуйста, задавайте уточняющие вопросы! Я надеюсь, что кто-то сможет помочь мне найти достойный способ справиться с этим. Все предложения приветствуются.
4 ответа
В верхней части потока обработки каждой страны поместите код страны в отображенный диагностический контекст 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(), когда ваша работа начинает устанавливать имя Thread? Если есть проблема с доступом, настройте кварц на использование собственного пула потоков.
Возможно, я совершенно не уверен в том, что понимаю, чего ты пытаешься достичь, но я попробую это решение. Похоже, вы хотите отдельный файл журнала для каждой страны, для которой вы обрабатываете электронную почту. Исходя из этого понимания, вот возможное решение:
Настройте appender в конфигурации 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 =% м% п
Создайте регистратор для каждой страны и направьте каждого из них соответствующему заявителю (пример для США):
log4j.logger.my-нам-регистратор = отлаживать usfile
В своем коде создайте свой регистратор на основе страны, для которой обрабатывается электронное письмо:
Logger logger = Logger.getLogger ("my-us-logger");
Определите, как вы будете выполнять шаг 3 для последующих вызовов методов. Вы можете повторить шаг 3 в каждом классе / методе; или вы могли бы изменить сигнатуры метода, чтобы принимать регистратор в качестве входных данных; или вы могли бы использовать ThreadLocal для передачи Logger между методами.
Дополнительная информация: Если вы не хотите, чтобы операторы журнала передавались родительским регистраторам (например, rootLogger), вы можете установить для их флагов аддитивности значение false (приведен пример для США):
log4j.additivity.my-us-logger=false