Печать имени потока с использованием java.util.logging - PullRequest
12 голосов
/ 31 июля 2011

Можно ли напечатать имя потока в операторах журнала, сгенерированных с помощью java.util.logging.Logger?

Одна альтернатива - сделать что-то вроде следующего:

logger.info(thread.getName() + " some useful info");

, но оно повторяется иструктура ведения журнала должна справиться с этим.

Ответы [ 7 ]

7 голосов
/ 31 июля 2011

Смущающе, но похоже, что java.util.logging не может этого сделать ...

По умолчанию java.util.logging.SimpleFormatter вообще не имеет возможности регистрировать имя потока вообще.java.util.logging.FileHandler поддерживает несколько шаблонных заполнителей, ни один из них не является именем потока.

java.util.logging.XMLFormatter является ближайшим, но регистрируется только идентификатор потока:

<record>
  <date>2011-07-31T13:15:32</date>
  <millis>1312110932680</millis>
  <sequence>0</sequence>
  <logger></logger>
  <level>INFO</level>
  <class>java.util.logging.LogManager$RootLogger</class>
  <method>log</method>
  <thread>10</thread>
  <message>Test</message>
</record>

Если вы считаете, что мы 'мы приближаемся - мы нет.LogRecord класс содержит только идентификатор потока, а не его имя - не очень полезно.

3 голосов
/ 09 сентября 2011

У меня была похожая проблема.Как ответили здесь Как выровнять сообщения журнала, используя java.util.logging , вы можете расширить java.util.logging.Formatter, но вместо получения LogRecord#getThreadID() вы можете получить имя потока, вызвав Thread.currentThread().getName(), например:

public class MyLogFormatter extends Formatter
{

    private static final MessageFormat messageFormat = new MessageFormat("[{3,date,hh:mm:ss} {2} {0} {5}]{4} \n");

    public MyLogFormatter()
    {
        super();
    }

    @Override
    public String format(LogRecord record)
    {
        Object[] arguments = new Object[6];
        arguments[0] = record.getLoggerName();
        arguments[1] = record.getLevel();
        arguments[2] = Thread.currentThread().getName();
        arguments[3] = new Date(record.getMillis());
        arguments[4] = record.getMessage();
        arguments[5] = record.getSourceMethodName();
        return messageFormat.format(arguments);
    }

}
2 голосов
/ 31 июля 2011

java.util.logging имеет много любопытных особенностей. Вы можете добавить API фасада, чтобы настроить его поведение

public class Log

    Logger logger;

    static public Log of(Class clazz)
        return new Log( Logger.getLogger( clazz.getName() ));

    public void error(Throwable thrown, String msg, Object... params)
    {
        log(ERROR, thrown, msg, params);
    }

    void log(Level level, Throwable thrown, String msg, Object... params)
    {
        if( !logger.isLoggable(level) ) return;

        // bolt on thread name somewhere
        LogRecord record = new LogRecord(...);
        record.setXxx(...);
        ...
        logger.log(record);
    }

----

static final Log log = Log.of(Foo.class);
....
log.error(...);

Люди используют журналирование java главным образом потому, что не хотят иметь сторонние зависимости. Вот почему они не могут зависеть от существующих фасадов, таких как apache или slf4j.

2 голосов
/ 31 июля 2011

Некоторые серверы приложений неявно регистрируют идентификатор потока (я знаю WebSphere). Вы можете создать свой собственный LogFormatter . Записи, передаваемые в средство форматирования, содержат идентификатор потока, см. здесь . Я реализовал этот подход для Tomcat несколько раз, но он будет работать и в средах Java SE.

Кстати: имя потока недоступно для LogRecord.

1 голос
/ 27 апреля 2019

С пользовательским Formatter

К счастью, LogRecord содержит идентификатор потока, который создал сообщение журнала.Мы можем получить это LogRecord при написании пользовательского Formatter.Как только мы это получим, нам нужно только получить имя потока через его идентификатор.

Существует пара способов , чтобы получить объект Thread, соответствующийэтот идентификатор, вот мой:

static Optional<Thread> getThread(long threadId) {
    return Thread.getAllStackTraces().keySet().stream()
            .filter(t -> t.getId() == threadId)
            .findFirst();
}

Ниже приведен минимальный Formatter, который печатает только имя потока и сообщение журнала:

private static Formatter getMinimalFormatter() {
    return new Formatter() {

        @Override
        public String format(LogRecord record) {

            int threadId = record.getThreadID();
            String threadName = getThread(threadId)
                    .map(Thread::getName)
                    .orElseGet(() -> "Thread with ID " + threadId);

            return threadName + ": " + record.getMessage() + "\n";
        }
    };
}

Чтобы использовать свой пользовательский форматтер, естьснова различные параметры , один из способов - изменить значение по умолчанию ConsoleHandler:

public static void main(final String... args) {

    getDefaultConsoleHandler().ifPresentOrElse(
            consoleHandler -> consoleHandler.setFormatter(getMinimalFormatter()),
            () -> System.err.println("Could not get default ConsoleHandler"));

    Logger log = Logger.getLogger(MyClass.class.getName());
    log.info("Hello from the main thread");
    SwingUtilities.invokeLater(() -> log.info("Hello from the event dispatch thread"));
}

static Optional<Handler> getDefaultConsoleHandler() {
    // All the loggers inherit configuration from the root logger. See:
    // https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html#a1.3
    var rootLogger = Logger.getLogger("")
    // The root logger's first handler is the default ConsoleHandler
    return first(Arrays.asList(rootLogger.getHandlers()));
}

static <T> Optional<T> first(List<T> list) {
    return list.isEmpty() ?
            Optional.empty() :
            Optional.ofNullable(list.get(0));
}

Ваш минимальный Formatter должен затем создать следующие сообщения журнала, содержащие имя потока:

main: Привет из основного потока

и

AWT-EventQueue-0: Привет из потока диспетчеризации событий


Это Formatter, который показывает, как регистрировать больше, чем имя потока и сообщение журнала:

private static Formatter getCustomFormatter() {
    return new Formatter() {

        @Override
        public String format(LogRecord record) {

            var dateTime = ZonedDateTime.ofInstant(record.getInstant(), ZoneId.systemDefault());

            int threadId = record.getThreadID();
            String threadName = getThread(threadId)
                    .map(Thread::getName)
                    .orElse("Thread with ID " + threadId);

            // See also: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html
            var formatString = "%1$tF %1$tT %2$-7s [%3$s] %4$s.%5$s: %6$s %n%7$s";

            return String.format(
                    formatString,
                    dateTime,
                    record.getLevel().getName(),
                    threadName,
                    record.getSourceClassName(),
                    record.getSourceMethodName(),
                    record.getMessage(),
                    stackTraceToString(record)
            );
        }
    };
}

private static String stackTraceToString(LogRecord record) {
    final String throwableAsString;
    if (record.getThrown() != null) {
        var stringWriter = new StringWriter();
        var printWriter = new PrintWriter(stringWriter);
        printWriter.println();
        record.getThrown().printStackTrace(printWriter);
        printWriter.close();
        throwableAsString = stringWriter.toString();
    } else {
        throwableAsString = "";
    }
    return throwableAsString;
}

Что Formatter создает сообщения журнала, подобные этим:

2019-04-27 13:21:01 ИНФОРМАЦИЯ [AWT-EventQueue-0] package.ClassName.method: сообщение журнала

0 голосов
/ 28 октября 2015

В дополнение к @ l245c4l ответ: Вместо использования SimpleFormatter () используйте:

//fileHandler.setFormatter(new SimpleFormatter());

class MyFormatter extends Formatter {
    private final MessageFormat messageFormat = new MessageFormat("{0,date}, {0,time} {1} {2}: {3} [T:{4}] {5}\n");

    public String format(LogRecord record)
    {
        Object[] arguments = new Object[6];
        arguments[0] = new Date( record.getMillis() );
        arguments[1] = record.getSourceClassName();
        arguments[2] = record.getSourceMethodName();
        arguments[3] = record.getLevel();
        arguments[4] = Long.toString( Thread.currentThread().getId() );
        arguments[5] = record.getMessage();

        return messageFormat.format(arguments);
    }
}

fileHandler.setFormatter( new MyFormatter() ); 

Logger myLogger = Logger.getLogger("<LOGGER_NAME>");
myLogger.addHandler(fileHandler);

где T:{4} - идентификатор потока (аргумент 4).

0 голосов
/ 24 мая 2013

Несколько ответов выше предполагают, что LogRecord.getThreadId () возвращает значимый идентификатор потока, и все, что нам не хватает, - это способ соотнести это с именем потока.

К сожалению, LogRecord.getThreadId () возвращает значение типа int, которое не соответствует длинному идентификатору потока, вызвавшего сообщение журнала.

Таким образом, мы не можем просто использовать ManagementFactory.getThreadMXBean () для разрешения имени потока. Это приводит к случайным именам потоков.

Если вы уверены, что ваше средство ведения журнала всегда форматирует в том же потоке, что и вызывающая сторона, то вы можете создать пользовательский модуль форматирования, как предложено выше, и вызвать Thread.currentThread (). GetName ().

Кажется, что фасад Logging или сторонняя библиотека - единственные полностью безопасные варианты.

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