Использование MDC в log4j для динамического присвоения имени файлу журнала

#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 — Как перенаправить сообщение в файл журнала, созданный динамически.