Статьи

Лучшее выполнение вызовов Logger без регистрации в Log4j2

Использование средств  защиты журналов  является обычной практикой при использовании  Log4j 1.x  и стремлении избежать дополнительного влияния на производительность, которое может возникнуть в некоторых случаях, даже если сообщение фактически не зарегистрировано. Одной из наиболее привлекательных функций, которые  Simple Logging Facade для Java  ( SLF4J ) привнес в протоколирование Java, была возможность  уменьшить количество  обстоятельств, при которых необходимы эти проверки уровня журнала. В этом посте, я смотрю на то, как можно использовать  Log4j 2.x «s  каротаж API изменения  для достижения аналогичного эффекта.

Следующий листинг кода демонстрирует ведение журнала длительных операций. Первый пример неявно вызывает toString() метод для экземпляра с именем «slow». Второй пример записи в журнал вызывает метод, который работает долго.

Традиционная, неохраняемая лесозаготовка

// Will implicitly invoke slow's toString() method  
logger.debug("NORMAL: " + slow);  
// Will explicitly invoke the long-running method expensiveOperation()  
logger.debug(expensiveOperation()); 

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

Есть два способа справиться с этим в Log4j 1.x. Один из подходов, и часто лучший, состоит в том, чтобы попытаться переписать оператор log, чтобы  не выполнять длительные операции . Когда это нецелесообразно (например, когда  нужен контекст,  связанный с длительной операцией, чтобы сообщение журнала было полезным), можно использовать средства защиты журналов. Этот подход, который работает в Log4j 1.x, демонстрируется следующим.

Традиционная охраняемая лесозаготовка

if (logger.isDebugEnabled())  
{  
   logger.debug("GUARDED: " + slow);  
   logger.debug(expensiveOperation());  
}  

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

Одна из наиболее распространенных ситуаций, когда вызовы журнала, которые на самом деле ничего не регистрируют, но существенно влияют на производительность, — это когда  метод toString () объекта  вызывается явно или неявно, когда объект передается в вызов регистратора или объединяется со строкой, переданной в вызов логгера. Эта ситуация была продемонстрирована в двух приведенных выше листингах кода со строкой, переданной в вызов регистратора, путем конкатенации строкового литерала «GUARDED:» в неявно вызываемый  toString() метод переменной с именем «slow».

SLF4J  имеет  популяризировал  концепцию  параметризованных лесозаготовительных  вызовов и Log4j 2 обеспечивает  аналогичную поддержку  в его регистрации API. Следующий код демонстрирует, как это используется.

Параметризованная регистрация

logger.debug("PARAMETERIZED: {}", slow);  
logger.debug("{}", expensiveOperation());

Когда приведенный выше пример параметризованного ведения журнала выполняется с уровнем записи, более специфичным, чем DEBUG, неявное  toString() использование переменной «slow» не будет выполнено из-за параметризованного ведения журнала. Однако параметризованное ведение журнала не может помочь в другой ситуации ведения журнала, так как метод expensiveOperation() будет вызван, несмотря на параметризованное ведение журнала. Также обратите внимание, что хотя параметризованное ведение журнала помогает в случае неявных  toString() вызовов, оно  не помогает  при явных toString() вызовах. Даже если уровень ведения журнала более специфичен, чем DEBUG, обращение к  slow.toString() оператору logger все равно повлечет за собой снижение производительности.

В Log4j 2.4  представлен механизм  , основанный на  лямбда-выражениях, который можно использовать для отсрочки вызова методов, переданных в вызов регистратора, так что их вообще не нужно выполнять, если оператор регистрируется на уровне, менее специфичном, чем текущий уровень журнала. , Это продемонстрировано в следующем листинге кода, где  toString()метод явно expensiveOperation вызывается для объекта «медленной» переменной через лямбда-выражение, а метод вызывается через  ссылку на метод .

Регистрация лямбда-выражений

logger.debug("LAMBDA: ", () -> slow.toString());  
logger.debug("{}", this::expensiveOperation);  

Когда приведенный выше код выполняется с уровнем журнала, установленным на более конкретный уровень, чем DEBUG, ни метод «медленного» объекта,  toString() ни  expensiveOperation метод не будут вызваны благодаря отложенной загрузке на основе лямбда-выражений. Другими словами, подобно тому, как пример работал с защитниками, использование лямбда-выражений предотвращает ненужное выполнение потенциально длительных методов, если их результаты действительно не будут регистрироваться. Эта   поддержка лямбда-выражений была добавлена ​​в Log4j с версией 2.4 и, конечно, требует  Java 8 .

Резюме

Log4j 2 (2.4) предоставляет несколько подходов, позволяющих избежать влияния на производительность операторов журнала, когда сообщения фактически не регистрируются.

  1. Операторы журнала могут быть переписаны так, чтобы дорогие методы (включая дорогие  toString()вызовы) вообще не регистрировались.
  2. Защитные средства ведения журнала могут использоваться для обеспечения того, чтобы вызовы длительных методов оператора журнала выполнялись только в том случае, если сообщение действительно должно быть зарегистрировано.
  3. Параметризованный (отформатированный) логгер API Log4j2 можно использовать для исключения вызова неявных toString() методов, если сообщение действительно не регистрируется.
  4. Log4j 2.4 API регистратора лямбда-выражений можно использовать для исключения вызова любых операций (явных или неявных), необходимых для зарегистрированного сообщения, если только сообщение не регистрируется.