Log4j'de, günlüğe kaydetmeden önce isDebugEnabled öğesinin denetlenmesi performansı artırır mı?


207

Günlük kaydı için uygulamamda Log4J kullanıyorum . Daha önce gibi hata ayıklama çağrısı kullanıyordum:

Seçenek 1:

logger.debug("some debug text");

ancak bazı bağlantılar isDebugEnabled()ilk önce kontrol etmenin daha iyi olduğunu gösterir , örneğin:

Seçenek 2:

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

Benim sorum " Seçenek 2 performansı herhangi bir şekilde geliştiriyor mu? "

Çünkü her durumda Log4J çerçevesi debugEnabled için aynı kontrole sahiptir. Seçenek 2 için, çerçevenin isDebugEnabled()yöntemi birden çok kez çağırması gerekmediği tek bir yöntemde veya sınıfta birden çok hata ayıklama deyimi kullanmamız yararlı olabilir (her çağrıda); bu durumda isDebugEnabled()yöntemi yalnızca bir kez çağırır ve Log4J düzeyi hata ayıklamak üzere yapılandırılmışsa, aslında isDebugEnabled()yöntemi iki kez çağırır :

  1. DebugEnabled değişkenine değer atanması durumunda ve
  2. Aslında logger.debug () yöntemi tarafından çağrılır.

Ben logger.debug()yöntem veya sınıf ve çağrı debug()yöntemi seçenek 1 göre birden çok ifade yazmak o zaman seçenek 2 ile karşılaştırıldığında Log4J çerçeve için yük olduğunu düşünmüyorum isDebugEnabled()(kod açısından) çok küçük bir yöntem olduğundan, olabilir inlining için iyi bir aday olun.

Yanıtlar:


247

Bu özel durumda, Seçenek 1 daha iyidir.

Koruma ifadesi (kontrol isDebugEnabled()), toString()çeşitli nesnelerin yöntemlerinin çağrılmasını ve sonuçların birleştirilmesini içerdiğinde günlük mesajının potansiyel olarak pahalı hesaplanmasını önlemek için vardır .

Verilen örnekte, günlük mesajı sabit bir dizedir, bu nedenle günlükçünün onu atmasına izin vermek, günlükçünün etkin olup olmadığını kontrol etmek kadar etkilidir ve daha az dal olduğundan kodun karmaşıklığını azaltır.

Daha da iyisi, günlük deyimlerinin bir biçim belirtimi ve kaydedici tarafından değiştirilecek argümanların bir listesini aldığı daha güncel bir günlükleme çerçevesi kullanmaktır - ancak yalnızca günlükleyici etkinse "tembel". Slf4j tarafından benimsenen yaklaşım budur .

Daha fazla bilgi için ilgili bir soruya vereceğim yanıtı ve log4j ile böyle bir şey yapmanın bir örneğini görün.


3
log5j sl44 ile aynı şekilde log4j'yi genişletir
Bill Michell

Bu aynı zamanda java.util.Logging yaklaşımıdır.
Paul

@Geek Günlük düzeyi yüksek ayarlandığından günlük olayı devre dışı bırakıldığında daha etkilidir. Cevabımdaki "Koşullu günlük kaydı ihtiyacı" başlıklı bölüme bakın .
erickson

1
Bu log4j 2'de değişti mi?
SnakeDoc

3
Yöntem çağrısı için esastır: yöntem arg-listelerindeki ifadeler çağrıdan önce etkili bir şekilde değerlendirilir. Bu ifadeler a) pahalı sayılırsa ve b) yalnızca belirli koşullar altında istenirse (örneğin hata ayıklama etkinleştirildiğinde), tek seçeneğiniz çağrının etrafına bir koşul kontrolü koymaktır ve çerçeve bunu sizin için yapamaz. Formatlayıcı tabanlı günlük yöntemleriyle ilgili olan şey, bazı Nesneleri (aslında ücretsizdir) iletebilmenizdir ve kaydedici toString()yalnızca gerekirse çağırır .
SusanW

31

Seçenek 1'de ileti dizesi bir sabit olduğundan, günlük deyimini bir koşulla sarmalamak kesinlikle bir kazanç değildir, aksine, günlük deyimi hata ayıklama etkinse, iki kez, bir kez isDebugEnabled()yöntemde ve bir kez debug()yöntem. Çağırma maliyeti, isDebugEnabled()en pratik amaçlar için ihmal edilebilir olması gereken 5 ila 30 nanosaniye civarındadır. Bu nedenle, seçenek 2 istenmez, çünkü kodunuzu kirletir ve başka bir kazanç sağlamaz.


17

Kullanılması isDebugEnabled()Eğer Dizeleri birleştirerek günlük iletilerini kadar bina yaparken için ayrılmıştır:

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

Ancak, örneğinizde, yalnızca bir Dize günlüğe kaydettiğiniz ve birleştirme gibi işlemleri gerçekleştirmediğiniz için hız kazancı yoktur. Bu nedenle, kodunuza sadece şişkinlik ekliyor ve okumayı zorlaştırıyorsunuz.

Şahsen String sınıfındaki Java 1.5 biçim çağrılarını şu şekilde kullanıyorum:

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

Çok fazla optimizasyon olduğundan şüpheliyim ama okumak daha kolay.

Ancak çoğu günlüğe kaydetme API'sının kutunun dışında bunun gibi biçimlendirme sunduğunu unutmayın: Örneğin slf4j aşağıdakileri sağlar:

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

ki bu daha kolay okunur.


8
String.format (...) kullanımınızı, günlük satırını okumayı kolaylaştırırken, aslında performansı kötü bir şekilde etkileyebilir. Bunu yapmanın SLF4J yolu, parametreleri logger.debug yöntemine gönderir ve burada dize oluşturulmadan önce isDebugEnabled değerlendirmesi yapılır . Bunu yaptığınız şekilde, String.format (...) ile, logger.debug yöntem çağrısı yapılmadan önce dize oluşturulur, böylece hata ayıklama düzeyi olsa bile dize binasının cezasını ödersiniz. etkin değil.
Nit

2
String.format concat'tan 40 kat daha yavaştır ve slf4j 2 param sınırlaması vardır Buradaki sayılara bakın: stackoverflow.com/questions/925423/… Üretim sistemi, INFO veya ERROR log seviyesinde çalışıyor
AztecWarrior_25 10:18


8

Kısa Versiyon: Boolean isDebugEnabled () kontrolünü de yapabilirsiniz.

Nedenleri:
1- Karmaşık mantık / string concat ise. hata ayıklama ifadenize eklendiğinde, check-in işlemi zaten yapılır.
2- "Karmaşık" hata ayıklama ifadeleriyle ilgili ifadeyi seçici olarak dahil etmek zorunda değilsiniz. Tüm ifadeler bu şekilde dahil edilir.
3- Log.debug çağrısı, günlüğe kaydetmeden önce aşağıdakileri yürütür:

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

Bu temel olarak arama günlüğü ile aynıdır. veya kedi. isDebugEnabled ().

ANCAK! Log4j geliştiricileri böyle düşünüyor (javadoclarında olduğu gibi ve muhtemelen onun tarafından geçmelisiniz.)

Bu yöntem

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

Bu onun için javadok

/**
*  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'u eklediğiniz için teşekkür ederiz. Bu tavsiyeyi daha önce bir yerde gördüğümü biliyordum ve kesin bir referans bulmaya çalışıyordum. Bu kesin olmasa bile, en azından çok iyi bilgilendirilmiş.
Simon Peter Chappell

7

Diğerlerinin de belirttiği gibi guard deyimi, yalnızca dize oluşturmak zaman alan bir çağrı ise gerçekten yararlıdır. Bunun özel örnekleri, dizeyi oluştururken bazı tembel yüklemeleri tetikleyeceğidir.

Java için Basit Logging Facade veya (SLF4J) - http://www.slf4j.org/manual.html kullanılarak bu sorunun önlenebileceğini belirtmek gerekir . Bu, aşağıdaki gibi yöntem çağrılarına izin verir:

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

Bu, yalnızca hata ayıklama etkinse iletilen parametreleri dizelere dönüştürür. SLF4J adından da anlaşılacağı gibi sadece bir cephedir ve loglama çağrıları log4j'ye aktarılabilir.

Bunun çok kolay bir "kendi kendi rulo" sürümünü de.

Bu yardımcı olur umarım.


6

Seçenek 2 daha iyidir.

Kendi başına performansı arttırmaz. Ancak performansın düşmemesini sağlar. İşte böyle.

Normalde logger.debug (someString);

Ancak genellikle, uygulama büyüdükçe, birçok el değiştirir, esp acemi geliştiriciler, görebilirsiniz

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

ve benzerleri.

Günlük düzeyi HATA veya FATAL olarak ayarlanmış olsa bile, dizelerin birleştirilmesi gerçekleşir! Uygulama dize birleştirme ile çok fazla DEBUG düzeyi mesaj içeriyorsa, özellikle jdk 1.4 veya daha düşük bir performans isabet alır. (Jdk internall'ın sonraki sürümlerinin herhangi bir stringbuffer.append () yapıp yapmadığından emin değilim).

Bu yüzden Seçenek 2 güvenlidir. Dizge birleştirme bile gerçekleşmez.


3

@Erickson gibi buna bağlı. Hatırlıyorsam, isDebugEnabledzaten debug()Log4j yönteminde inşa edilmiştir .
Hata ayıklama ifadelerinizde nesneler üzerinde döngü, hesaplamalar yapma ve dizeleri birleştirme gibi bazı pahalı hesaplamalar yapmadığınız sürece, bence sorun yok.

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

olarak daha iyi olurdu

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

Bir İçin tek bir satır , ben birleştirme yapmayın Bu şekilde, mesaj giriş bir üçlü içini kullanın:

eJ:

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

Yaparım:

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

Ancak birden fazla kod satırı için

EJ.

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

Yaparım:

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

3

Birçok kişi log4j2'yi ararken muhtemelen bu yanıtı görüntülediğinden ve neredeyse tüm geçerli cevaplar log4j2'yi veya son zamanlarda yapılan değişiklikleri dikkate almazsa, bu umarım soruyu cevaplamalıdır.

log4j2 Tedarikçi'yi destekler (şu anda kendi uygulamaları, ancak belgelere göre Java'nın 3.0 sürümünde Tedarikçi arayüzünü kullanması planlanmaktadır). Kılavuzda bununla ilgili biraz daha fazla bilgi bulabilirsiniz . Bu, yalnızca günlük kaydedilecekse iletiyi oluşturan bir tedarikçiye pahalı günlük mesajı oluşturmanıza izin verir:

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

2

Hızı artırır, çünkü hata ayıklama metninde pahalı olan dizeleri birleştirmek yaygındır, örneğin:

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

1
Eğer jdk 1.5 ve sonrası kullanıyorsanız, o zaman çok dizeleri herhangi bir fark olmayacak düşünüyorum.
Sessiz Savaşçı

Nasıl olur? JDK5 farklı ne yapardı?
javashlook

1
Eğer tek bir deyimde dizeleri birleştirirsek jdk 1.5 sonra dahili olarak sadece StringBuffer.append () yöntemini kullanır. Dolayısıyla performansı etkilemez.
Sessiz Savaşçı

2
Dize birleştirme şüphesiz zaman alır. Ancak bunu 'pahalı' olarak tanımlayacağımdan emin değilim. Yukarıdaki örnekte ne kadar zaman kazanılır? Çevredeki kodun gerçekte ne yaptığına kıyasla? (örneğin, veritabanı okumaları veya bellek içi hesaplama). Bence bu tür ifadelerin nitelendirilmesi gerekiyor
Brian Agnew

1
JDK 1.4 bile basit dize birleşimi ile yeni String nesneleri oluşturmaz. Performans cezası, hiçbir dize gösterilmemesi gerektiğinde StringBuffer.append () kullanıldığında gelir.
javashlook

1

Yana Log4J sürümü 2.4(veya slf4j-api 2.0.0-alpha1) çok daha iyi kullanmak için; akıcı API (veya Java tembel günlük için 8 lambda desteği ), destekleyici Supplier<?>tarafından verilebilir günlük mesajı argüman için lambda :

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

VEYA slf4j API ile:

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

0

Seçenek 2'yi kullanırsanız, hızlı bir Boole kontrolü gerçekleştirirsiniz. Birinci seçenekte bir yöntem çağrısı yapıyorsunuz (yığına bir şeyler itiyorsunuz) ve sonra hala hızlı olan bir Boole kontrolü yapıyorsunuz. Gördüğüm sorun tutarlılık. Hata ayıklama ve bilgi ifadelerinizden bazıları sarılmış ve bazıları değilse tutarlı bir kod stili değildir. Ayrıca daha sonra birisi, hala oldukça hızlı olan bitiştirme dizelerini içerecek şekilde hata ayıklama deyimini değiştirebilir. Büyük bir uygulamada hata ayıklama ve bilgi ifadesini tamamladığımızda ve profillediğimizde performansta birkaç puan kazandığımızı fark ettim. Fazla değil, ama işe değer yapmak için yeterli. Şimdi otomatik olarak benim için sarılmış hata ayıklama ve bilgi ifadeleri oluşturmak için IntelliJ birkaç makro kurulum var.


0

Süper pahalı değil gibi Option 2 de fiili olarak kullanmanızı tavsiye ederim.

Durum 1: log.debug ("bir dize")

Durum2: log.debug ("bir dize" + "iki dize" + object.toString + object2.toString)

Bunlardan herhangi biri çağrıldığında, log.debug içindeki parametre dizesi (CASE 1 veya Case2 olsun) değerlendirilmelidir. Herkesin 'pahalı' ile kastettiği bu. Öncesinde bir koşul varsa, 'isDebugEnabled ()', performansın kaydedildiği yerlerin bunların değerlendirilmesi gerekmez.


0

2.x itibariyle, Apache Log4j'de bu kontrol yerleşiktir, bu yüzden isDebugEnabled()artık gerekli değildir. Sadece bir yapın debug()ve etkinleştirilmezse mesajlar bastırılır.


-1

Log4j2, parametreleri bir mesaj şablonuna benzer şekilde biçimlendirmenize izin verir String.format(), böylece yapılması gerekeni ortadan kaldırır isDebugEnabled().

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

Basit log4j2 örnek özellikleri:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.