#java #log4j #mdc
#java #log4j #mdc
Вопрос:
Возможно ли использовать MDC для присвоения имени файлу журнала во время выполнения.
У меня есть одно веб-приложение, которое одновременно вызывается разными именами с использованием tomcat docbase. Поэтому мне нужно иметь отдельные файлы журнала для каждого из них.
Ответ №1:
Это может быть выполнено в Logback, преемнике Log4J.
Logback задуман как преемник популярного проекта log4j, начиная с того места, где log4j останавливается.
Смотрите Документацию по отбору Appender
SiftingAppender уникален своей способностью ссылаться на вложенные приложения и настраивать их. В приведенном выше примере внутри SiftingAppender будут вложенные экземпляры FileAppender, каждый экземпляр которых идентифицируется значением, связанным с ключом MDC «userid». Всякий раз, когда ключу MDC «userid» присваивается новое значение, новый экземпляр FileAppender будет создан с нуля. SiftingAppender отслеживает созданные им приложения. Приложения, неиспользуемые в течение 30 минут, будут автоматически закрыты и удалены.
В примере они генерируют отдельный файл журнала для каждого пользователя на основе значения MDC. В зависимости от ваших потребностей могут использоваться другие значения MDC.
Комментарии:
1. Но использование Logback будет означать, что все операторы ведения журнала должны быть изменены, верно? `
Ответ №2:
Это также возможно с log4j. Вы можете сделать это, внедрив свой собственный аппендер. Я думаю, самый простой способ — создать подкласс AppenderSkeleton.
Все события ведения журнала заканчиваются в append(LoggingEvent event)
методе, который вы должны реализовать.
В этом методе вы можете получить доступ к MDC с помощью event.getMDC("nameOfTheKeyToLookFor");
Затем вы можете использовать эту информацию, чтобы открыть файл для записи. Может быть полезно взглянуть на реализацию стандартных приложений, таких как RollingFileAppender, чтобы выяснить остальное.
Я сам использовал этот подход в приложении для разделения журналов разных потоков на разные файлы журналов, и он работал очень хорошо.
Комментарии:
1. Я не использую RollingFileAppender. Возможно ли это в FileAppender?
2. Я упомянул RollingFileAppender только в качестве примера реализации приложения. В принципе, вы можете делать все, что хотите, в методе добавления.
Ответ №3:
Некоторое время я пытался найти в log4j функциональность, подобную SiftingAppender (мы не могли переключиться на logback из-за некоторых зависимостей), и в итоге получил программное решение, которое работает довольно хорошо, используя MDC и добавляя регистраторы во время выполнения:
// this can be any thread-specific string
String processID = request.getProcessID();
Logger logger = Logger.getRootLogger();
// append a new file logger if no logger exists for this tag
if(logger.getAppender(processID) == null){
try{
String pattern = "%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n";
String logfile = "log/" processID ".log";
FileAppender fileAppender = new FileAppender(
new PatternLayout(pattern), logfile, true);
fileAppender.setName(processID);
// add a filter so we can ignore any logs from other threads
fileAppender.addFilter(new ProcessIDFilter(processID));
logger.addAppender(fileAppender);
}catch(Exception e){
throw new RuntimeException(e);
}
}
// tag all child threads with this process-id so we can separate out log output
MDC.put("process-id", processID);
//whatever you want to do in the thread
LOG.info("This message will only end up in " processID ".log!");
MDC.remove("process-id");
Добавленный выше фильтр просто проверяет определенный идентификатор процесса:
public class RunIdFilter extends Filter {
private final String runId;
public RunIdFilter(String runId) {
this.runId = runId;
}
@Override
public int decide(LoggingEvent event) {
Object mdc = event.getMDC("run-id");
if (runId.equals(mdc)) {
return Filter.ACCEPT;
}
return Filter.DENY;
}
}
Надеюсь, это немного поможет.
Комментарии:
1. Как мне написать тот же фильтр в Log4j 2?
2. @NaiveCoder в любом месте, где вы хотите запустить фильтрацию MDC.
Ответ №4:
По состоянию на 20-01-2020 это теперь функция Log4j по умолчанию.
Для достижения этого вам просто нужно использовать RoutingAppender с MDC.
Пример:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Appenders>
<Routing name="Analytics" ignoreExceptions="false">
<Routes>
<Script name="RoutingInit" language="JavaScript"><![CDATA[
// This script must return a route name
//
// Example from https://logging.apache.org/log4j/2.x/manual/appenders.html#RoutingAppender
// on how to get a MDC value
// logEvent.getContextMap().get("event_type");
//
// but as we use only one route with dynamic name, we return 1
1
]]>
</Script>
<Route>
<RollingFile
name="analytics-${ctx:event_type}"
fileName="logs/analytics/${ctx:event_type}.jsonl"
filePattern="logs/analytics/$${date:yyyy-MM}/analytics-${ctx:event_type}-%d{yyyy-dd-MM-}-%i.jsonl.gz">
<PatternLayout>
<pattern>%m%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Route>
</Routes>
<!-- Created appender TTL -->
<IdlePurgePolicy timeToLive="15" timeUnit="minutes"/>
</Routing>
</Appenders>
<Loggers>
<Logger name="net.bytle.api.http.AnalyticsLogger" level="debug" additivity="false">
<AppenderRef ref="Analytics"/>
</Logger>
</Loggers>
</Configuration>
Чтобы узнать больше, см. Log4j — Как перенаправить сообщение в файл журнала, созданный динамически.