У log4j чи перевірка isDebugEnabled перед входом у систему покращує продуктивність?


207

Я використовую Log4J у своїй програмі для ведення журналів. Раніше я використовував виклик налагодження на зразок:

Варіант 1:

logger.debug("some debug text");

але деякі посилання підказують, що краще спочатку перевірити isDebugEnabled(), наприклад:

Варіант 2:

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text");
}

Тож моє запитання: " Чи покращує продуктивність 2 спосіб? ".

Тому що в будь-якому випадку рамки Log4J мають однакову перевірку на налагодження. Для варіанту 2 це може бути корисно, якщо ми використовуємо кілька операторів налагодження в одному методі або класі, де фреймворк не потребує виклику isDebugEnabled()методу кілька разів (під час кожного виклику); у цьому випадку він викликає isDebugEnabled()метод лише один раз, і якщо Log4J налаштований на рівень налагодження, він фактично викликає isDebugEnabled()метод двічі:

  1. У разі присвоєння значення debugEnabled змінної та
  2. Фактично викликається методом logger.debug ().

Я не думаю, що якщо ми пишемо декілька logger.debug()операторів у методі чи класі та debug()методі виклику відповідно до варіанту 1, то це накладні витрати на рамку Log4J порівняно з варіантом 2. Оскільки isDebugEnabled()це дуже маленький метод (з точки зору коду), він може бути хорошим кандидатом на вступ.

Відповіді:


247

У цьому конкретному випадку варіант 1 кращий.

Заява захисту (перевірка isDebugEnabled()) існує для запобігання потенційно дорогого обчислення повідомлення журналу, коли воно передбачає виклик toString()методів різних об'єктів та об'єднання результатів.

У наведеному прикладі повідомлення журналу - це постійний рядок, тому відпустити реєстратор відкинути це так само ефективно, як перевірити, чи ввімкнено реєстратор, і це знижує складність коду, оскільки там менше гілок.

Ще краще - використовувати більш сучасні рамки ведення журналів, де оператори журналу приймають специфікацію формату та список аргументів, які підлягають заміні реєстратора - але "ліниво", лише якщо ввімкнено реєстратор. Це підхід, який застосовує slf4j .

Дивіться мою відповідь на пов'язане запитання для отримання додаткової інформації та приклад того, як щось подібне робити з log4j.


3
log5j розширює log4j приблизно так само, як slf4j
Білл Мішель

Це також підхід java.util.Logging.
Пол

@Geek Це ефективніше, коли подія журналу відключена, оскільки рівень журналу встановлений високим. Дивіться розділ під назвою "Необхідність умовного ведення журналу" в моїй відповіді тут.
erickson

1
Чи змінилося це в log4j 2?
SnakeDoc

3
@SnakeDoc Ні. Це важливо для виклику методу: вирази в аргументах списку методів ефективно оцінюються перед викликом. Якщо ці вирази а) вважаються дорогими і б) потрібні лише за певних умов (наприклад, коли налагоджено налагодження), тоді ваш єдиний вибір - встановити перевірку стану навколо виклику, і рамки не можуть цього зробити для вас. Справа з методами журналу на основі форматерів полягає в тому, що ви можете передавати деякі Об'єкти (які, по суті, безкоштовні), і реєстратор зателефонує toString()лише за потреби.
SusanW

31

Оскільки у варіанті 1 рядок повідомлення є постійною, абсолютно не виграш у загортанні оператора журналу з умовою, навпаки, якщо увімкнено налагодження оператора журналу, ви будете оцінювати двічі, один раз у isDebugEnabled()методі та один раз у debug()метод. Вартість виклику isDebugEnabled()становить приблизно від 5 до 30 наносекунд, що має бути незначним для більшості практичних цілей. Таким чином, варіант 2 небажаний, оскільки він забруднює ваш код і не забезпечує іншого вигоди.


17

Використання isDebugEnabled()зарезервованого для, коли ви збираєте повідомлення журналу шляхом об'єднання рядків:

Var myVar = new MyVar();
log.debug("My var is " + myVar + ", value:" + myVar.someCall());

Однак у вашому прикладі немає збільшення швидкості, оскільки ви просто реєструєте рядок і не виконуєте операцій, таких як конкатенація. Тому ви просто додаєте коду до свого коду і ускладнюєте його читання.

Я особисто використовую дзвінки формату Java 1.5 у класі String так:

Var myVar = new MyVar();
log.debug(String.format("My var is '%s', value: '%s'", myVar, myVar.someCall()));

Я сумніваюся, що оптимізація є значною, але її легше читати.

Зауважте, що більшість API журналів пропонують таке форматування з поля: наприклад, slf4j надає наступне:

logger.debug("My var is {}", myVar);

що ще простіше читати.


8
Використання String.format (...), полегшуючи читання рядка журналу, може насправді погано впливати на продуктивність. Спосіб виконання SLF4J надсилає параметри в метод logger.debug і там оцінюється isDebugEnabled, перш ніж будується рядок. Так, як ви це робите, з String.format (...), рядок буде побудовано до виклику методу logger.debug, так що ви заплатили штраф будівлі рядків, навіть якщо рівень налагодження становить не ввімкнено. Вибачте за збирання ніт, просто намагаючись уникнути плутанини для новачків ;-)
StFS

2
String.format в 40 разів повільніше, ніж concat & slf4j має обмеження на 2 парами. Див. Номери тут: stackoverflow.com/questions/925423/… Я бачив багато графіків профайлерів, де операція формату витрачається в операторах налагодження, коли виробнича система є працює на рівні журналу INFO або ПОМИЛКА
AztecWarrior_25


8

Коротка версія: Ви також можете зробити булеву перевірку isDebugEnabled ().

Причини:
1- Якщо складна логіка / строка. додано до вашої заяви про налагодження, ви вже матимете чек.
2- Вам не доведеться вибірково включати вислів у "складні" заяви про налагодження. Усі твердження включаються таким чином.
3- Виклик log.debug виконує такі дії перед входом у систему:

if(repository.isDisabled(Level.DEBUG_INT))
return;

Це в основному те саме, що і журнал викликів. або кіт. isDebugEnabled ().

ЗАРАЗ! Це вважають розробники log4j (як це є у їхньому javadoc, і ви, ймовірно, повинні пройти це.)

Це метод

public
  boolean isDebugEnabled() {
     if(repository.isDisabled( Level.DEBUG_INT))
      return false;
    return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel());
  }

Це явадок для цього

/**
*  Check whether this category is enabled for the <code>DEBUG</code>
*  Level.
*
*  <p> This function is intended to lessen the computational cost of
*  disabled log debug statements.
*
*  <p> For some <code>cat</code> Category object, when you write,
*  <pre>
*      cat.debug("This is entry number: " + i );
*  </pre>
*
*  <p>You incur the cost constructing the message, concatenatiion in
*  this case, regardless of whether the message is logged or not.
*
*  <p>If you are worried about speed, then you should write
*  <pre>
*    if(cat.isDebugEnabled()) {
*      cat.debug("This is entry number: " + i );
*    }
*  </pre>
*
*  <p>This way you will not incur the cost of parameter
*  construction if debugging is disabled for <code>cat</code>. On
*  the other hand, if the <code>cat</code> is debug enabled, you
*  will incur the cost of evaluating whether the category is debug
*  enabled twice. Once in <code>isDebugEnabled</code> and once in
*  the <code>debug</code>.  This is an insignificant overhead
*  since evaluating a category takes about 1%% of the time it
*  takes to actually log.
*
*  @return boolean - <code>true</code> if this category is debug
*  enabled, <code>false</code> otherwise.
*   */

1
Дякуємо, що включили JavaDoc. Я знав, що я бачив цю пораду десь раніше і намагався знайти остаточне посилання. Це, якщо не остаточне, принаймні дуже добре поінформоване.
Саймон Пітер Чаппелл

7

Як вже згадували інші, використання оператора Guard є корисним лише тоді, коли створення рядка вимагає багато часу. Конкретні приклади цього - коли створення рядка спричинить деяке ледаче завантаження.

Варто зазначити, що цю проблему можна уникнути, скориставшись фасадом простого журналу для Java або (SLF4J) - http://www.slf4j.org/manual.html . Це дозволяє виклики методів, такі як:

logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);

Це перетворить передані параметри лише у рядки, якщо налагоджено налагодження. SLF4J, як випливає з назви, - це лише фасад, і виклики реєстрації можуть бути передані log4j.

Ви також можете дуже легко "прокатати свою" версію цього.

Сподіваюсь, це допомагає.


6

Варіант 2 кращий.

Сама по собі це не покращує продуктивність. Але це гарантує, що продуктивність не погіршується. Ось як.

Зазвичай ми очікуємо logger.debug (someString);

Але зазвичай, у міру того, як додаток росте, змінюється багато рук, розробники-початківці, особливо ви могли бачити

logger.debug (str1 + str2 + str3 + str4);

тощо.

Навіть якщо для рівня журналу встановлено значення ERROR або FATAL, об'єднання рядків відбувається! Якщо програма містить безліч повідомлень рівня DEBUG з рядковими конкатенаціями, вона, безумовно, сприймає продуктивність, особливо з jdk 1.4 або нижче. (Я не впевнений, що пізніші версії jdk internall роблять будь-який stringbuffer.append ()).

Ось чому варіант 2 безпечний. Навіть струнні конкатенації не трапляються.


3

Як і @erickson, це залежить. Якщо я пам'ятаю, isDebugEnabledце вже побудований у debug()методі Log4j.
Поки ви не робите дорогих обчислень у своїх заявах про налагодження, наприклад, циклічність на об'єктах, виконання обчислень та об'єднання рядків, на мою думку, ви добре.

StringBuilder buffer = new StringBuilder();
for(Object o : myHugeCollection){
  buffer.append(o.getName()).append(":");
  buffer.append(o.getResultFromExpensiveComputation()).append(",");
}
log.debug(buffer.toString());

було б краще як

if (log.isDebugEnabled(){
  StringBuilder buffer = new StringBuilder();
  for(Object o : myHugeCollection){
    buffer.append(o.getName()).append(":");
    buffer.append(o.getResultFromExpensiveComputation()).append(",");
  }
  log.debug(buffer.toString());
}

3

Для одного рядка я використовую потрійне всередині журналу повідомлення, таким чином я не роблю з'єднання:

ej:

logger.debug(str1 + str2 + str3 + str4);

Я згоден:

logger.debug(logger.isDebugEnable()?str1 + str2 + str3 + str4:null);

Але для декількох рядків коду

ej.

for(Message mess:list) {
    logger.debug("mess:" + mess.getText());
}

Я згоден:

if(logger.isDebugEnable()) {
    for(Message mess:list) {
         logger.debug("mess:" + mess.getText());
    }
}

3

Оскільки багато людей, ймовірно, переглядають цю відповідь під час пошуку log4j2 і майже всі поточні відповіді не враховують log4j2 або останні зміни в ньому, сподіваємось, це відповість на питання.

log4j2 підтримує постачальників s (зараз їх власна реалізація, але згідно з документацією планується використовувати інтерфейс постачальника Java у версії 3.0). Ви можете прочитати трохи більше про це в посібнику . Це дозволяє помістити створення дорогого журналу повідомлення в постачальника, який створює повідомлення лише в тому випадку, якщо воно буде зареєстровано:

LogManager.getLogger().debug(() -> createExpensiveLogMessage());

2

Це підвищує швидкість, тому що звичайно об'єднувати рядки в тексті налагодження, що дорого, наприклад:

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text" + someState);
}

1
Якщо ми використовуємо jdk 1.5 і далі, я вважаю, що об'єднані рядки не матимуть ніякої різниці.
Мовчазний воїн

Як це? Що б JDK5 зробив інакше?
javashlook

1
У jdk 1.5, якщо ми об'єднуємо рядки в одне твердження, то внутрішньо він використовує лише метод StringBuffer.append (). Тож це не впливає на продуктивність.
Мовчазний воїн

2
З'єднання струн, безперечно, вимагає часу. Однак я не впевнений, що назвав би це "дорогим". Скільки часу економиться у наведеному вище прикладі? Порівняно з тим, що насправді робить навколишній код? (наприклад, читання бази даних або обчислення в пам'яті). Я думаю, що подібні твердження потрібно кваліфікувати
Брайан Агнеу

1
Навіть JDK 1.4 не створюватиме нові об'єкти String з простим об'єднанням рядків. Штраф за продуктивність застосовується за допомогою StringBuffer.append (), коли жодна рядок взагалі не повинна відображатися.
javashlook

1

Оскільки у версії Log4j2.4 (або slf4j-api 2.0.0-alpha1) набагато краще використовувати вільний API (або підтримку лямбда-журналу Java 8 для ледачого ведення журналу ), що підтримує Supplier<?>аргумент повідомлення журналу, який може надати lambda :

log.debug("Debug message with expensive data : {}", 
           () -> doExpensiveCalculation());

АБО за допомогою API slf4j:

log.atDebug()
            .addArgument(() -> doExpensiveCalculation())
            .log("Debug message with expensive data : {}");

0

Якщо ви використовуєте варіант 2, ви робите булеву перевірку, яка швидко. У першому варіанті ви здійснюєте виклик методу (натискання на стек), а потім здійснюєте булеву перевірку, яка все ще є швидкою. Я бачу проблему - послідовність. Якщо деякі ваші заяви про налагодження та інформацію завернуті, а деякі - ні, це не є послідовним стилем коду. Крім того, хтось пізніше міг би змінити операцію налагодження, щоб включити об'єднані рядки, що все ще досить швидко. Я виявив, що, коли ми завершили налагодження та інформацію про інформацію у великій програмі та профілювали її, ми заощадили пару відсоткових очок у продуктивності. Не багато, але достатньо, щоб зробити його вартим роботи. Зараз у мене є пара налаштування макросів в IntelliJ, щоб автоматично генерувати обернені дані про налагодження та інформацію для мене.


0

Я б рекомендував використовувати варіант 2 як фактично для більшості, оскільки це не надто дорого.

Випадок 1: log.debug ("одна рядок")

Case2: log.debug ("одна рядок" + "дві рядки" + object.toString + object2.toString)

Під час виклику будь-якого з них слід обчислити рядок параметра в log.debug (будь то CASE 1 або Case2). Ось, що всі мають на увазі під «дорого». Якщо ви маєте перед цим умову "isDebugEnabled ()", їх не потрібно оцінювати, де зберігається продуктивність.


0

Станом на 2.x, в Apache Log4j вбудований цей чек, тому isDebugEnabled()більше не потрібен. Просто зробіть це, debug()і повідомлення будуть придушені, якщо їх не ввімкнено.


-1

Log4j2 дозволяє форматувати параметри в шаблон повідомлення, подібний до String.format(), таким чином, усуваючи необхідність isDebugEnabled().

Logger log = LogManager.getFormatterLogger(getClass());
log.debug("Some message [myField=%s]", myField);

Зразок простих log4j2.properties:

filter.threshold.type = ThresholdFilter
filter.threshold.level = debug
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d %-5p: %c - %m%n
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = debug
rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.