Статьи

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

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

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

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

1
2
3
4
// 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, демонстрируется следующим.

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

1
2
3
4
5
if (logger.isDebugEnabled())
{
   logger.debug("GUARDED: " + slow);
   logger.debug(expensiveOperation());
}

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

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

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

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

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

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

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

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

1
2
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 регистратора лямбда-выражений можно использовать для исключения вызова любых операций (явных или неявных), необходимых для зарегистрированного сообщения, если только сообщение не регистрируется.