Как записать несколько потоков в разные файлы журнала? - PullRequest
7 голосов
/ 02 декабря 2011

У меня есть класс JAVA, который запускает различные потоки с уникальными идентификаторами.Каждый поток должен войти в уникальный файл журнала, названный в честь ID.log.

Поскольку я получаю уникальный идентификатор только во время выполнения, я должен настроить Log4J программно:

// Get the jobID
myJobID = aJobID;
// Initialize the logger
myLogger = Logger.getLogger(myJobID);
FileAppender myFileAppender;
try
{
    myFileAppender = new FileAppender(new SimpleLayout(), myJobID + ".log", false);
    BasicConfigurator.resetConfiguration();
    BasicConfigurator.configure(myFileAppender);
} catch (IOException e1) {
// TODO Auto-generated catch block
    e1.printStackTrace();
}

Сейчасэто работает нормально, если я запускаю задания последовательно - но когда я запускаю 2 потока (одного и того же класса) одновременно, оба журнала создаются, но журналы перепутаны: второй поток входит в первый и второй журналы.

Как я могу убедиться, что каждый экземпляр уникален?Я уже пытался дать уникальное имя каждому экземпляру регистратора, но это ничего не изменило.

Ответы [ 6 ]

13 голосов
/ 04 декабря 2011

Logback имеет специальный аппендер под названием SiftingAppender , который обеспечивает очень хорошее решение для типа проблем, которые вы описываете. SiftingAppender может использоваться для разделения (или просеивания) ведения журнала в соответствии с любым атрибутом среды выполнения, включая идентификатор потока.

8 голосов
/ 03 марта 2014

Для log4j v2 вы можете использовать RoutingAppender для динамической маршрутизации сообщений.Вы можете поместить значение ключа 'threadId' в карту ThreadContext, а затем использовать этот идентификатор как часть имени файла.Есть пример, который я легко применил для той же цели, что и ваш.См. http://logging.apache.org/log4j/2.x/faq.html#separate_log_files

При размещении значений в карте ThradContext помните: «Дочерний поток автоматически наследует копию сопоставленного диагностического контекста своего родителя».Таким образом, если вы поместили значение ключа 'threadId' в родительский поток и в итоге создали из него несколько потоков, то все дочерние потоки будут наследовать значение значения 'threadId'.Я не смог просто переопределить это значение, используя put () еще раз - вам нужно использовать ThreadContext.clear () или явно удалить () значение из контекстной карты потока.

Вот мой рабочий log4j.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="WARN">
    <properties>
        <property name="logMsgPattern">%d{HH:mm:ss} %-5level - %msg%n</property>
        <property name="logDir">test logs</property><!-- ${sys:testLogDir} -->
    </properties>
    <appenders>
         <Console name="Console" target="SYSTEM_OUT">           
            <PatternLayout pattern="${logMsgPattern}"/>
         </Console>

        <Routing name="Routing">
                    <Routes pattern="$${ctx:threadId}">             
                        <Route>
                            <RollingFile name="RollingFile-${ctx:threadId}" fileName="${logDir}/last-${ctx:threadId}.log" filePattern="${logDir}/%d{yyyy-MM-dd}/archived_%d{HH-mm}-${ctx:threadId}.log">
                                    <PatternLayout pattern="${logMsgPattern}"/>
                                    <Policies>
                                <OnStartupTriggeringPolicy />
                            </Policies> 
                    </RollingFile>
                        </Route>
                    </Routes>
            </Routing>  
    </appenders>

    <loggers>               
        <root level="debug">
            <appender-ref ref="Console" level="debug" />
            <appender-ref ref="Routing" level="debug"/>
        </root>                     
    </loggers>  
</configuration>
5 голосов
/ 02 декабря 2011
Подход

@ havexz довольно хорош: запись всего в один и тот же файл журнала и использование вложенных диагностических контекстов .

Если вы беспокоитесь о записи нескольких JVM в один и тот же FileAppender,тогда я бы предложил две вещи:

В разумном режиме FileAppender будет выполнять безопасную запись в указанный файл, даже при наличии других экземпляров FileAppender, работающих на разных виртуальных машинах Java, потенциально работающих на разных хостах.

3 голосов
/ 01 июня 2017

Вот фрагмент кода маршрутизации из рабочего файла log4j.xml.

<Appenders>

        <Console name="ConsoleAppender" target="SYSTEM_OUT">
            <PatternLayout>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %-50logger{4}: %msg%n</pattern>
            </PatternLayout>
        </Console>
    <Routing name="RoutingAppender">
                <Routes pattern="${ctx:logFileName}">


                    <!-- This route is chosen if ThreadContext has a value for logFileName. 
                        The value dynamically determines the name of the log file. -->

                    <Route>
                        <RollingFile name="Rolling-${ctx:logFileName}"
                            fileName="${sys:log.path}/${ctx:logFileName}.javalog"
                            filePattern="./logs/${date:yyyy-MM}/${ctx:logFileName}_%d{yyyy-MM-dd}-%i.log.gz">
                            <PatternLayout>
                                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %-50logger{4}: %msg%n</pattern>
                            </PatternLayout>
                            <Policies>
                                <TimeBasedTriggeringPolicy interval="6"
                                    modulate="true" />
                                <SizeBasedTriggeringPolicy size="10 MB" />
                            </Policies>
                        </RollingFile>
                    </Route>

                    <!-- This route is chosen if ThreadContext has no value for key logFileName. -->
                    <Route key="${ctx:logFileName}" ref="ConsoleAppender" />

                </Routes>
            </Routing>
</Appenders>
    <loggers>
        <root level="debug">
            <appender-ref ref="RoutingAppender" level="debug" />
        </root>
    </loggers>

Ключ 'logFileName' можно добавить к контекстной карте потока в методе run () класса Runnable следующим образом:

public class SomeClass implements Runnable{

 private int threadID;

 public SomeClass(int threadID){
   this.threadID=threadID;
   }
 @Override
 public void run() {
    String logFileName = "thread_log_"+ threadID;
    ThreadContext.put("logFileName", logFileName);
    //Some code
    ThreadContext.remove("threadId");
   }
}

Кроме того, необходимо импортировать правильные пакеты log4j, как показано ниже.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;

Обратите внимание, что следующий импорт не будет работать. LogManager и Logger также должны быть из org.apache.logging.log4j.

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
0 голосов
/ 02 декабря 2011

Насколько я могу сказать, ThreadLocal API был разработан для того, что вы описываете.

Код, подобный приведенному ниже, установил бы логгеры для каждого потока, каждый из которых использовал бы собственный (для каждого потока) FileAppender:

/**
 * usage: threadLocalLogger.get().info("hello thread local logger")
 */
static ThreadLocal<Logger> threadLocalLogger = newThreadLocalLogger("myJobId");

private static ThreadLocal<Logger> newThreadLocalLogger(final String myJobID) {
    return new ThreadLocal<Logger>() {
        @Override
        protected Logger initialValue() {
            return logger(myJobID, Thread.currentThread().getId());
        }
    };
}

private static Logger logger(String myJobID, long threadId) {
    // Initialize the logger
    String loggerId = myJobID + "-" + threadId;
    Logger myLogger = Logger.getLogger(loggerId);
    FileAppender myFileAppender;
    try
    {
        myFileAppender = new FileAppender(new SimpleLayout(),
                loggerId + ".log", false);
        BasicConfigurator.resetConfiguration();
        BasicConfigurator.configure(myFileAppender);
    } catch (IOException e1) {
    // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    return myLogger;
}
0 голосов
/ 02 декабря 2011

Как насчет добавления статической переменной счетчика экземпляров в ваш класс. Тогда вам потребуется синхронизированный метод, который увеличивает счетчик для каждого созданного объекта и создает имя файла журнала из этого значения. Примерно так:

class yourClass {

  private static int cnt = 0;

  public yourClass(){
    ...
    initLogger();
  }

  private synchronized initLogger(){
     yourClass.cnt++;
     myJobid = yourClass.cnt;

     //include your logging code here
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...