Подтвердить что ты не робот

Почему вызов LoggerFactory.getLogger(...) каждый раз не рекомендуется?

Я прочитал множество сообщений и документов (на этом сайте и в других местах), указав, что рекомендуемый шаблон для регистрации SFL4J:

public class MyClass {
    final static Logger logger = LoggerFactory.getLogger(MyClass.class);

    public void myMethod() {
        //do some stuff
        logger.debug("blah blah blah");
    }
}

Мой босс предпочитает, чтобы мы просто использовали обертку для перехвата вызовов журналов и избегали кода плиты котла для объявления журнала в каждом классе:

public class MyLoggerWrapper {
    public static void debug(Class clazz, String msg){
        LoggerFactory.getLogger(clazz).debug(msg));
    }
}

и просто используя его следующим образом:

public class MyClass {

    public void myMethod() {
        //do some stuff
        MyLoggerWrapper.debug(this.getClass(), "blah blah blah");
    }
}

Я предполагаю, что при создании журнала каждый раз, когда мы регистрируемся, несколько дороже, но я не смог найти документацию, подтверждающую это предположение. Кроме того, он уверен, что рамки (LogBack или Log4J, которые мы все еще решаем) будут "кэшировать" регистраторы, а также, что в любом случае серверы работают значительно ниже их емкости, поэтому это не проблема.

Любая помощь, указывающая на потенциальные проблемы с этим подходом?

4b9b3361

Ответ 1

Вот одна очевидная проблема с этим подходом: сообщения String будут построены при каждом вызове debug(), нет очевидного способа использования предложения guard с вашей оберткой.

Стандартная идиома с log4j/commons-logging/slf4j заключается в использовании предложения охраны, такого как:

if (log.isDebugEnabled()) log.debug("blah blah blah");

С тем, что если уровень DEBUG не включен для регистратора, компилятор может избежать объединения друг с другом любых более длинных строк, которые вы можете отправить:

if (log.isDebugEnabled()) log.debug("the result of method foo is " + bar 
     + ", and the length is " + blah.length());

См. "Каков самый быстрый способ (не) ведения журнала?" в SLF4J или log4j FAQ.

Я бы рекомендовал против "обертки", который предлагает ваш босс. Библиотека, подобная slf4j или commons-logging, уже является фасадом вокруг фактической базовой реализации каротажа. Кроме того, каждый вызов регистратора становится намного длиннее - сравните приведенное выше с

 MyLoggerWrapper.debug(Foo.class, "some message");

Это тип тривиальной и несущественной "обертывания" и обфускации, которая не служит никакой реальной цели, кроме добавления слоев косвенности и уродливости вашего кода. Я думаю, ваш босс может найти более важные проблемы, чтобы одержимость.

Ответ 2

Объекты журнала наверняка будут повторно использованы, поэтому никакая дополнительная настройка не произойдет в любом случае. Большая проблема, которую я вижу, заключается в том, что ваша информация о номере файла/строки будет бесполезной, поскольку журнал всегда будет записывать журнал, что каждое сообщение было отправлено из класса LoggerWrapper, строка 12: - (

OTOH SLF4J уже является оберточным фасадом, чтобы скрыть конкретную используемую фреймворк, позволяя свободно изменять между различными реализациями ведения журнала. Поэтому я не вижу абсолютно никакого смысла скрывать это позади еще одной обертки.

Ответ 3

Повторные вызовы LoggerFactory.getLogger(clazz) не должны приводить к появлению нового объекта Logger каждый раз. Но это не означает, что призывы бесплатны. Хотя фактическое поведение зависит от системы ведения журнала за фасадом, весьма вероятно, что каждый getLogger влечет за собой поиск в параллельной или синхронизированной структуре данных 1 чтобы искать уже существующий экземпляр.

Если ваше приложение делает много вызовов вашего метода MyLoggerWrapper.debug, это может привести к значительному результату. И в многопоточном приложении это может быть узкое место concurrency.

Также важны другие вопросы, упомянутые в других ответах:

  • Ваше приложение больше не может использовать logger.isDebugEnabled(), чтобы минимизировать накладные расходы, когда отладка отключена.
  • Класс MyLoggerWrapper скрывает имена классов и номера строк отладочных вызовов приложения.
  • Код с использованием MyLoggerWrapper, вероятно, будет более подробным, если вы сделаете несколько вызовов журнала. И многословие будет в той области, где оно больше всего влияет на читаемость; то есть в методах, которые выполняют операции, требующие протоколирования.

Наконец, это просто "не так, как это делается".


1 - По-видимому, это Hashtable в Logback и Log4j, а это означает, что потенциал для узкого места concurrency определенно существует. Обратите внимание, что это не критика этих фреймворков. Скорее, метод getLogger не был разработан/оптимизирован для использования таким образом.

Ответ 4

Чтобы добавить к упомянутым причинам, ваше предложение босса плохо, потому что:

  • Это заставляет вас многократно вводить то, что не имеет ничего общего с протоколированием, каждый раз, когда вы хотите что-то записывать: this.getClass()
  • Создает неравномерный интерфейс между статическими и нестационарными контекстами (поскольку в статическом контексте нет this)
  • Дополнительные ненужные параметры создают место для ошибки, позволяет операторам того же класса перейти к разным регистраторам (подумайте о небрежном копировании)
  • Пока он сохраняет 74 символа объявления логгера, он добавляет 27 дополнительных символов к каждому вызову регистрации. Это означает, что если класс использует регистратор более 2 раз, вы увеличиваете код шаблона с точки зрения количества символов.

Ответ 5

При использовании чего-то вроде: MyLoggerWrapper.debug(this.getClass(), "blah" ) При использовании AOP-фреймворков или инструментов для ввода кода вы получите неправильные имена классов. Классовые имена не похожи на исходное, а сгенерированное имя класса. И еще один недостаток использования обертки: для каждого оператора журнала вы должны добавить дополнительный код "MyClass.class".

"Кэширование" регистраторов зависит от используемых фреймворков. Но даже когда это происходит, он все равно должен искать желаемый регистратор для каждого оператора журнала, который вы делаете. Таким образом, имея 3 оператора в методе, он должен искать его 3 раза. Используя его как статическую переменную, он должен искать его только один раз!

И сказал раньше: вы теряете возможность использовать if (log.isXXXEnabled()) {} для набора инструкций.

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

Ответ 6

Здесь есть одна возможность упростить ведение журнала в Java 8 - определить интерфейс, чтобы сделать это для вас. Например:

package logtesting;

import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public interface Loggable { 
    enum LogLevel {
        TRACE, DEBUG, INFO, WARN, ERROR
    }

    LogLevel TRACE = LogLevel.TRACE;
    LogLevel DEBUG = LogLevel.DEBUG;
    LogLevel INFO = LogLevel.INFO;
    LogLevel WARN = LogLevel.WARN;
    LogLevel ERROR = LogLevel.ERROR;

    default void log(Object...args){
        log(DEBUG, args);
    }

    default void log(final LogLevel level, final Object...args){
        Logger logger = LoggerFactory.getLogger(this.getClass());
        switch(level){
        case ERROR:
            if (logger.isErrorEnabled()){
                logger.error(concat(args));
            }
            break;
        case WARN:
            if (logger.isWarnEnabled()){
                logger.warn(concat(args));
            }
            break;          
        case INFO:
            if (logger.isInfoEnabled()){
                logger.info(concat(args));
            }
        case TRACE:
            if (logger.isTraceEnabled()){
                logger.trace(concat(args));
            }
            break;
        default:
            if (logger.isDebugEnabled()){
                logger.debug(concat(args));
            }
            break;
        }
    }

    default String concat(final Object...args){ 
        return Arrays.stream(args).map(o->o.toString()).collect(Collectors.joining());
    }
}

Затем все, что вам нужно сделать, это убедиться, что ваши классы объявляют о реализации. Записан, и из любого из них вы можете делать такие вещи, как:

log(INFO, "This is the first part ","of my string ","and this ","is the last");

Функция log() позаботится о конкатенации ваших строк, но только после того, как она проведет проверку для включенного. Он регистрируется для отладки по умолчанию, и если вы хотите вести журнал для отладки, вы можете опустить аргумент LogLevel. Это очень простой пример. Вы можете сделать все возможное, чтобы улучшить это, например, реализовать отдельные методы, то есть error(), trace(), warn() и т.д. Вы также можете просто реализовать "logger" как функцию, которая возвращает регистратор:

public interface Loggable {
    default Logger logger(){
        return LoggerFactory.getLogger(this.getClass());
    }
}

И тогда становится довольно тривиально использовать ваш регистратор:

logger().debug("This is my message");

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

package logtesting;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;

public interface Loggable extends Logger {
    default Logger logger(){
        return LoggerFactory.getLogger(this.getClass());
    }

    default String getName() {
        return logger().getName();
    }

    default boolean isTraceEnabled() {
        return logger().isTraceEnabled();
    }

    default void trace(String msg) {
        logger().trace(msg);
    }

    default void trace(String format, Object arg) {
        logger().trace(format, arg);
    }

    default void trace(String format, Object arg1, Object arg2) {
        logger().trace(format, arg1, arg2);
    }

    default void trace(String format, Object... arguments) {
        logger().trace(format, arguments);
    }

    default void trace(String msg, Throwable t) {
        logger().trace(msg, t);
    }

    default boolean isTraceEnabled(Marker marker) {
        return logger().isTraceEnabled(marker);
    }

    default void trace(Marker marker, String msg) {
        logger().trace(marker, msg);
    }

    default void trace(Marker marker, String format, Object arg) {
        logger().trace(marker, format, arg);
    }

    default void trace(Marker marker, String format, Object arg1, Object arg2) {
        logger().trace(marker, format, arg1, arg2);
    }

    default void trace(Marker marker, String format, Object... argArray) {
        logger().trace(marker, format, argArray);
    }

    default void trace(Marker marker, String msg, Throwable t) {
        logger().trace(marker, msg, t);
    }

    default boolean isDebugEnabled() {
        return logger().isDebugEnabled();
    }

    default void debug(String msg) {
        logger().debug(msg);
    }

    default void debug(String format, Object arg) {
        logger().debug(format, arg);
    }

    default void debug(String format, Object arg1, Object arg2) {
        logger().debug(format, arg1, arg2);
    }

    default void debug(String format, Object... arguments) {
        logger().debug(format, arguments);
    }

    default void debug(String msg, Throwable t) {
        logger().debug(msg, t);
    }

    default boolean isDebugEnabled(Marker marker) {
        return logger().isDebugEnabled(marker);
    }

    default void debug(Marker marker, String msg) {
        logger().debug(marker, msg);
    }

    default void debug(Marker marker, String format, Object arg) {
        logger().debug(marker, format, arg);
    }

    default void debug(Marker marker, String format, Object arg1, Object arg2) {
        logger().debug(marker, format, arg1, arg2);
    }

    default void debug(Marker marker, String format, Object... arguments) {
        logger().debug(marker, format, arguments);
    }

    default void debug(Marker marker, String msg, Throwable t) {
        logger().debug(marker, msg, t);
    }

    default boolean isInfoEnabled() {
        return logger().isInfoEnabled();
    }

    default void info(String msg) {
        logger().info(msg);
    }

    default void info(String format, Object arg) {
        logger().info(format, arg);
    }

    default void info(String format, Object arg1, Object arg2) {
        logger().info(format, arg1, arg2);
    }

    default void info(String format, Object... arguments) {
        logger().info(format, arguments);
    }

    default void info(String msg, Throwable t) {
        logger().info(msg, t);
    }

    default boolean isInfoEnabled(Marker marker) {
        return logger().isInfoEnabled(marker);
    }

    default void info(Marker marker, String msg) {
        logger().info(marker, msg);
    }

    default void info(Marker marker, String format, Object arg) {
        logger().info(marker, format, arg);
    }

    default void info(Marker marker, String format, Object arg1, Object arg2) {
        logger().info(marker, format, arg1, arg2);
    }

    default void info(Marker marker, String format, Object... arguments) {
        logger().info(marker, format, arguments);
    }

    default void info(Marker marker, String msg, Throwable t) {
        logger().info(marker, msg, t);
    }

    default boolean isWarnEnabled() {
        return logger().isWarnEnabled();
    }

    default void warn(String msg) {
        logger().warn(msg);
    }

    default void warn(String format, Object arg) {
        logger().warn(format, arg);
    }

    default void warn(String format, Object... arguments) {
        logger().warn(format, arguments);
    }

    default void warn(String format, Object arg1, Object arg2) {
        logger().warn(format, arg1, arg2);
    }

    default void warn(String msg, Throwable t) {
        logger().warn(msg, t);
    }

    default boolean isWarnEnabled(Marker marker) {
        return logger().isWarnEnabled(marker);
    }

    default void warn(Marker marker, String msg) {
        logger().warn(marker, msg);
    }

    default void warn(Marker marker, String format, Object arg) {
        logger().warn(marker, format, arg);
    }

    default void warn(Marker marker, String format, Object arg1, Object arg2) {
        logger().warn(marker, format, arg1, arg2);
    }

    default void warn(Marker marker, String format, Object... arguments) {
        logger().warn(marker, format, arguments);
    }

    default void warn(Marker marker, String msg, Throwable t) {
        logger().warn(marker, msg, t);
    }

    default boolean isErrorEnabled() {
        return logger().isErrorEnabled();
    }

    default void error(String msg) {
        logger().error(msg);
    }

    default void error(String format, Object arg) {
        logger().error(format, arg);
    }

    default void error(String format, Object arg1, Object arg2) {
        logger().error(format, arg1, arg2);
    }

    default void error(String format, Object... arguments) {
        logger().error(format, arguments);
    }

    default void error(String msg, Throwable t) {
        logger().error(msg, t);
    }

    default boolean isErrorEnabled(Marker marker) {
        return logger().isErrorEnabled(marker);
    }

    default void error(Marker marker, String msg) {
        logger().error(marker, msg);
    }

    default void error(Marker marker, String format, Object arg) {
        logger().error(marker, format, arg);
    }

    default void error(Marker marker, String format, Object arg1, Object arg2) {
        logger().error(marker, format, arg1, arg2);
    }

    default void error(Marker marker, String format, Object... arguments) {
        logger().error(marker, format, arguments);
    }

    default void error(Marker marker, String msg, Throwable t) {
        logger().error(marker, msg, t);
    }
}

Конечно, как упоминалось ранее, это означает, что каждый раз, когда вы регистрируетесь, вам придется пройти процесс поиска Logger в вашем LoggerFactory - если вы не переопределите метод logger()... в этом случае вы можете как хорошо сделайте это "рекомендуемым" способом.

Ответ 7

Я просто должен сказать, что рекомендуемый образец проще всего читать и реализовывать. Я не вижу причин отклоняться от него. Особенно выгодно.

Однако, мой главный момент касается охранников, упомянутых ранее. Я бы не рекомендовал явно защищать ваши журналы, поскольку это уже сделано внутренне с помощью log4j и является дублированием усилий.

Загрузите источник для log4j и посмотрите на классы Logger и Category, чтобы убедиться сами.

Ответ 8

2015 версия ответа: пожалуйста, освободите свой ум lombok @slf4j.

Ответ 9

Нет. Не что иное, как испортить стек вызовов. Это нарушает методы, позволяющие видеть имя метода и класс кода, выполняющего журнал.

Вы можете рассмотреть возможность просмотра веб-контейнера Jetty, который содержит собственную абстракцию, которая строится поверх slf4j. Очень приятно.

Ответ 10

Как указано здесь командой SLF4J вы можете использовать метод MethodLookup(), введенный в JDK 1.7.

final static Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

Таким образом вы можете обратиться к классу без использования ключевого слова "this".

Ответ 11

Есть две причины, почему подход вашего босса не достигает того, что он думает.

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

  • Найдите класс, т.е. пройдете все .jars и каталоги. Java-код. Довольно большие накладные расходы из-за вызовов файловой системы. Может создавать вспомогательные объекты, например. с File.
  • Загрузите байт-код, т.е. скопируйте его в структуру данных внутри JVM. Родной код.
  • Подтвердите байт-код. Родной код.
  • Свяжите байтовый код, т.е. повторите все имена классов в байт-коде и замените их указателями на упомянутый класс. Родной код.
  • Запустите статические инициализаторы. Исправлено из собственного кода, инициализаторы - это Java-код, конечно. Здесь создан Logger.
  • Через некоторое время, возможно, JIT-компиляция класса. Родной код. Огромные накладные расходы (по сравнению с другими операциями в любом случае).

Кроме того, ваш босс ничего не спасает.
Первый вызов LoggerFactor.getLogger создаст регистратор и поместит его в глобальную систему имен HagerMap. Это произойдет даже для вызовов isXxxEnabled, потому что для этого вам нужно сначала создать объект Logger...
Объект класса будет содержать дополнительный указатель для статической переменной. Это компенсируется накладными расходами на передачу параметра clazz - дополнительной инструкции и дополнительной ссылки указателя в байтовом коде, поэтому вы потеряете хотя бы один байт в классе.

Код также проходит через дополнительное косвенное обозначение, LoggerFactory.getLogger(Class) использует Class#getName и передает на LoggerFactory.getLogger(String).

Теперь, если ваш босс не работает, но после возможности просто скопировать статическое объявление, он может использовать функцию, которая проверяет стек вызовов и извлекает имя класса. Функция должна быть аннотирована @CallerSensitive, и она все еще должна быть проверена всякий раз, когда используется новая JVM - нехорошо, если вы не управляете JVM, на котором пользователь запускает код.

Наименее проблематичным подходом было бы иметь IDE, которая проверяет экземпляр регистратора. Это, вероятно, означает поиск или запись плагина.

Ответ 12

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

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