Отображать идентификатор потока вместо имени потока в журнале - программирование
Подтвердить что ты не робот

Отображать идентификатор потока вместо имени потока в журнале

У меня есть приложение Struts с log4j для отображения информации о приложении.

Рисунок для вывода журнала журнала выглядит следующим образом:

log4j.appender.RALL.layout.ConversionPattern=[%p] %d{dd/MM/yyyy HH:mm:ss} [THREAD ID=%t] [CLASS=(%C{1}:%L)] %m%n 

Мне нужно показать thread id вместо имя потока в журнале. Символ преобразования, отображающий имя потока, равен% t. Я не вижу в документации log4j способ ее получить.

Может ли кто-нибудь мне помочь?

4b9b3361

Ответ 1

Это возможно, но не так просто, как использование некоторых предварительно настроенных шаблонов.

Log4j 1.X и Log4j 2.x не имеют каких-либо предварительно настроенных шаблонов для печати идентификатора потока, но вы всегда можете использовать "магический трюк".

PatternLayout использует класс PatternParser который помечается как final класс и имеет статическую карту "паттернов" в качестве ключей, а классы Converters качестве значений. Каждый раз, когда Parses находит шаблон, использующий для регистрации формат шаблона, начинающийся с % он использует конвертер, соответствующий этому ключу шаблона в карте.

Вы не можете добавить свое собственное правило к этой карте, но вы все равно можете написать свой собственный MyOwnPatternLayout:

public class MyOwnPatternLayout extends PatternLayout

который будет в этом методе format делать такой трюк:

public String format(LoggingEvent event) {
   String log = super.format(event);
   /*
   Now you just have to replace with regex all occurences of %i or 
   any mark you would like to use as mark to represent Thread ID 
   with Thread ID value.
   Only thing you have to be sure to not use any mark as your Thread ID
   that already is defined by PatterParser class
   */
   return log.replaceAll("%i", someThreadID);
}

Единственная проблема заключается в том, что вы должны каким-то образом получить этот идентификатор потока. Иногда все, что вам нужно сделать, это проанализировать имя потока, которое вы можете легко собрать:

String threadName = event.getThreadName();

Например, Apache-Tomcat поместил идентификатор потока в конец имени потока http-nio-/127.0.0.1-8084 "-exec-41.

Чтобы быть уверенным в правильности идентификатора потока, вы также можете создать свой собственный подкласс LogginEvent и Logger (MyLoggingEvent и MyLogger), а внутри MyLogger создать MyLoggingEvent ведьма также будет принимать в качестве аргумента идентификатор потока, а не только имя потока. Тогда вы можете легко собрать его в коде выше.

Извините за длинный ответ, и я надеюсь, что это, по крайней мере, поможет вам.

Ответ 2

Один из способов сделать это - добавить его самостоятельно, используя log4j MDC. Мы используем его для добавления имени пользователя для веб-запросов. Мы делаем это в фильтре в начале каждого запроса. Например.

import org.apache.log4j.MDC;

...

  // Add username to MDC
  String username = ...;
  MDC.put("user", username);

Затем добавьте [%X{user}] в шаблон преобразования.

Ответ 4

Вы можете использовать карту ThreadContext для предоставления метаданных в log4j2. Это String Map значений, которые вы МОЖЕТЕ добавить с помощью обычного форматирования.

String threadId = String.valueOf(Thread.currentThread().getId());
ThreadContext.put("TId", threadId);

И гораздо более разумный шаблон:

    <PatternLayout pattern="%d{yyyyMMdd}T%d{HHmmss.SSS} %-5level [%t] [%5X{TId}] %15c{1} - %msg%n"/>

Полная документация по Log4j2 по теме "Маркировка рыб"

Ответ 5

Я думаю, что невозможно показать идентификатор потока со стандартным форматированием log4j. Я также исследовал код PatterParser, который не нашел ничего полезного. Я нашел несколько пользовательских решений, но только для сервера IBM, у которого есть опция %i:

% i: Вставляет идентификатор потока. В отличие от имени потока (указано% t), это числовой идентификатор потока. Обратите внимание, что этот параметр является особым для Initiate, в то время как другие параметры, перечисленные здесь, являются стандартными с log4j.

См. эта ссылка

Ответ 6

Разверните PatternLayout, как показано ниже, а затем укажите MyPatternLayout с $X{threadId} в строке формата.

В этой реализации используется ThreadLocal, чтобы свести к минимуму влияние производительности на вычисление ИД потока:

    MyPatternLayout extends PatternLayout {

        private final ThreadLocal<String> threadId = new ThreadLocal<String>() {

            @Override
            protected String initialValue() {
                String t = Long.toString(Thread.currentThread().getId());
                MDC.put("threadId", t);
                return t;
            }
        };

        @Override
        public String format(LoggingEvent event) {

            this.threadId.get();
            return super.format(event);
        }
    }

Ответ 7

Я не знаю, когда это будет введено, но в log4j2 у нас есть % tid для

Выводит идентификатор потока, который сгенерировал событие регистрации.

https://logging.apache.org/log4j/2.x/manual/layouts.html

Ответ 8

Одним из возможных решений является создание собственного класса, который находится между вашим кодом и Log4J и добавляет идентификатор потока к каждому сообщению журнала:

public class ThreadLogger
{
    // Constructor declared private to prevent instantiation.  Use static methods instead.
    private ThreadLogger() {}

    private static enum LogLevel
    {
        TRACE,
        DEBUG,
        INFO,
        WARN,
        ERROR
    }

    public static void trace(String message)
    {
        logMessage(message, LogLevel.ERROR);
    }

    public static void debug(String message)
    {
        logMessage(message, LogLevel.ERROR);
    }

    public static void info(String message)
    {
        logMessage(message, LogLevel.ERROR);
    }

    public static void warn(String message)
    {
        logMessage(message, LogLevel.WARN);
    }

    public static void error(String message)
    {
        logMessage(message, LogLevel.ERROR);
    }

    private static void logMessage(String message, LogLevel logLevel)
    {
        // Get the Log4J logger for the class that originally wanted to log the message
        String callingClassName = Thread.currentThread().getStackTrace()[3].getClassName();
        Class callingClass;
        try
        {
            callingClass = Class.forName(callingClassName);
        }
        catch(ClassNotFoundException e)
        {
            String errorMessage = String.format("Could not reference class [%s].  Unable to log call!", callingClassName);
            throw new RuntimeException(errorMessage);
        }
        Logger logger = Logger.getLogger(callingClass);

        // Get the thread ID and place it in front of the logged message
        long threadId = Thread.currentThread().getId();
        String formattedMessage = String.format("[%s] %s", threadId, message);

        // Log the message
        switch(logLevel)
        {
            case TRACE:
                logger.trace(formattedMessage);
                break;
            case DEBUG:
                logger.debug(formattedMessage);
                break;
            case INFO:
                logger.info(formattedMessage);
                break;
            case WARN:
                logger.warn(formattedMessage);
                break;
            case ERROR:
                logger.error(formattedMessage);
                break;
        }
    }
}

Downsides:

  • Производительность? Это добавляет несколько дополнительных шагов для каждого оператора журнала.
  • Стабильность? Это добавляет потенциальную точку отказа (вызов Class.forName).
  • Вы должны заменить все существующие записи журнала на вызовы новому классу.
  • Идентификатор потока не будет отображаться только после обычного форматирования Log4J. IE:

1234 [main] INFO com.foo.bar.Baz - [1] Hello world on thread #1!
1234 [main] INFO com.foo.bar.Baz - [2] Hello world on thread #2!

Ответ 9

Я создаю свой собственный appender и устанавливаю Thread.currentThread(). getId() в свойство MDC. % X {threadId} должен дать мне идентификатор потока. Это решение работает с 1.2.15. Затем вы можете присоединить AsyncAppender к этому.

public class CurrentThreadIdAppender extends AppenderSkeleton implements AppenderAttachable {

    private final AppenderAttachableImpl appenders = new AppenderAttachableImpl();

...

    @Override
    protected void append(LoggingEvent event) {   
        synchronized (appenders) {
            event.setProperty("threadId", String.valueOf(Thread.currentThread().getId()));
            appenders.appendLoopOnAppenders(event);
        }
    }

...

}

Ответ 10

Другим элегантным решением с log4j2 является использование org.apache.logging.log4j.core.pattern.LogEventPatternConverter.

Вы можете написать класс, подобный этому

@Plugin(name = "ThreadIdConverter", category = "Converter")
@ConverterKeys({ "tid" })
public class ThreadIdConverter extends LogEventPatternConverter {

    protected ThreadIdConverter(String name, String style) {
        super(name, style);
    }

    @Override
    public void format(LogEvent event, StringBuilder toAppendTo) {
        toAppendTo.append(getThreadId());
    }

    protected String getThreadId() {
        long id = Thread.currentThread().getId();
        return Long.toHexString(id);
    }

    public static ThreadIdConverter newInstance(String[] options) {
        return new ThreadIdConverter("tid", "tid");
    }
}

Таким образом вы создаете новый шаблон tid, и его можно использовать при определении макета вашего приложения

<Appenders>
    <Console name="console" target="SYSTEM_OUT">
        <PatternLayout>
            <Pattern>%d{dd-MMM HH:mm:ss.SSS} %-7level [%5tid] %logger - %message%n</Pattern>
        </PatternLayout>
    </Console>
</Appenders>

Последнее, что нужно помнить, - активировать ваш плагин log4j2. Для этого вам нужно добавить пакет, содержащий ваши плагины в файле конфигурации log4j2, используя атрибут package на Configuration node

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE Configuration>
<Configuration status="warn"
    packages="my.package.logging.plugins">
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout>
                <Pattern>%d{dd-MMM HH:mm:ss.SSS} %-7level [%5tid] %logger - %message%n</Pattern>
            </PatternLayout>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="warn">
            <AppenderRef ref="console" />
        </Root>
        <Logger name="my.package" level="trace" />
    </Loggers>
</Configuration>