Di log4j, apakah memeriksa isDebugEnabled sebelum masuk meningkatkan kinerja?

207

Saya menggunakan Log4J dalam aplikasi saya untuk login. Sebelumnya saya menggunakan panggilan debug seperti:

Pilihan 1:

logger.debug("some debug text");

tetapi beberapa tautan menyarankan bahwa lebih baik untuk memeriksa isDebugEnabled()dulu, seperti:

Pilihan 2:

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

Jadi pertanyaan saya adalah " Apakah opsi 2 meningkatkan kinerja? ".

Karena bagaimanapun kerangka Log4J memiliki pemeriksaan yang sama untuk debugEnabled. Untuk opsi 2 mungkin bermanfaat jika kita menggunakan beberapa pernyataan debug dalam metode atau kelas tunggal, di mana kerangka kerja tidak perlu memanggil isDebugEnabled()metode beberapa kali (pada setiap panggilan); dalam hal ini ia memanggil isDebugEnabled()metode hanya sekali, dan jika Log4J dikonfigurasi ke level debug maka sebenarnya ia memanggil isDebugEnabled()metode dua kali:

  1. Dalam hal memberikan nilai ke variabel debugEnabled, dan
  2. Sebenarnya disebut dengan metode logger.debug ().

Saya tidak berpikir bahwa jika kita menulis beberapa logger.debug()pernyataan dalam metode atau kelas dan memanggil debug()metode menurut opsi 1 maka itu adalah overhead untuk kerangka kerja Log4J dibandingkan dengan opsi 2. Karena isDebugEnabled()merupakan metode yang sangat kecil (dalam hal kode), mungkin menjadi kandidat yang baik untuk inlining.

Prajurit Diam
sumber

Jawaban:

248

Dalam kasus khusus ini, Opsi 1 lebih baik.

Pernyataan penjaga (memeriksa isDebugEnabled()) ada untuk mencegah kemungkinan perhitungan mahal dari pesan log ketika melibatkan doa daritoString() metode berbagai objek dan menyatukan hasilnya.

Dalam contoh yang diberikan, pesan log adalah string konstan, jadi membiarkan logger membuangnya sama efisiennya dengan memeriksa apakah logger diaktifkan, dan menurunkan kompleksitas kode karena jumlah cabang yang lebih sedikit.

Lebih baik lagi adalah menggunakan kerangka kerja logging yang lebih mutakhir di mana pernyataan log mengambil spesifikasi format dan daftar argumen yang akan diganti oleh logger — tetapi "malas," hanya jika logger diaktifkan. Ini adalah pendekatan yang diambil oleh slf4j .

Lihat jawaban saya untuk pertanyaan terkait untuk informasi lebih lanjut, dan contoh melakukan sesuatu seperti ini dengan log4j.

erickson
sumber
3
log5j meluas log4j dalam banyak cara yang sama seperti slf4j
Bill Michell
Ini juga merupakan pendekatan java.util.Logging.
Paul
@Geek Lebih efisien bila acara log dinonaktifkan karena tingkat log ditetapkan tinggi. Lihat bagian berjudul "Perlunya logging bersyarat" dalam jawaban
erickson
1
Apakah ini berubah di log4j 2?
SnakeDoc
3
@SnakeDoc No. Ini mendasar untuk pemanggilan metode: ekspresi dalam metode arg-list dievaluasi secara efektif sebelum pemanggilan. Jika ekspresi itu a) dianggap mahal dan b) hanya diinginkan dalam kondisi tertentu (misalnya ketika debug diaktifkan), maka satu-satunya pilihan Anda adalah melakukan pemeriksaan kondisi di sekitar panggilan, dan kerangka kerja tidak dapat melakukannya untuk Anda. Masalahnya dengan metode log berbasis formatter adalah bahwa Anda dapat melewati beberapa Object (yang pada dasarnya gratis), dan logger akan memanggil toString()hanya jika diperlukan.
SusanW
31

Karena dalam opsi 1 string pesan adalah konstan, sama sekali tidak ada keuntungan dalam membungkus pernyataan logging dengan suatu kondisi, sebaliknya, jika pernyataan log diaktifkan debug, Anda akan mengevaluasi dua kali, sekali dalam isDebugEnabled()metode dan sekali dalam debug()metode. Biaya memohon isDebugEnabled()dalam urutan 5 hingga 30 nanodetik yang harus diabaikan untuk tujuan paling praktis. Dengan demikian, opsi 2 tidak diinginkan karena mencemari kode Anda dan tidak memberikan keuntungan lainnya.

Ceki
sumber
17

Menggunakan tanda isDebugEnabled()disediakan untuk ketika Anda membangun pesan log dengan merangkai Strings:

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

Namun, dalam contoh Anda tidak ada peningkatan kecepatan karena Anda hanya mencatat String dan tidak melakukan operasi seperti penggabungan. Karena itu Anda hanya menambahkan mengasapi kode Anda dan membuatnya lebih sulit untuk dibaca.

Saya pribadi menggunakan panggilan format Java 1.5 di kelas String seperti ini:

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

Saya ragu ada banyak optimasi tetapi lebih mudah dibaca.

Harap perhatikan bahwa sebagian besar API logging menawarkan format seperti ini di luar kotak: slf4j misalnya menyediakan yang berikut ini:

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

yang bahkan lebih mudah dibaca.

Alex
sumber
8
Penggunaan String.format (...) Anda, sembari membuat garis log lebih mudah dibaca, sebenarnya bisa berdampak buruk pada kinerja. Cara SLF4J melakukan ini mengirim parameter ke metode logger.debug dan di sana evaluasi isDebugEnabled dilakukan sebelum string dibangun. Cara Anda melakukannya, dengan String.format (...), string akan dibangun sebelum pemanggilan metode untuk logger.debug dilakukan sehingga Anda akan membayar penalti pembuatan string bahkan jika level debug adalah tidak diaktifkan. Maaf untuk memilih nit, hanya mencoba untuk menghindari kebingungan untuk pemula ;-)
StFS
2
String.format 40 kali lebih lambat daripada concat & slf4j memiliki batasan 2 params. Lihat nomor di sini: stackoverflow.com/questions/925423/ ... Saya telah melihat banyak grafik profiler di mana operasi format terbuang dalam pernyataan debug ketika sistem produksi diaktifkan berjalan pada tingkat log INFO atau ERROR
AztecWarrior_25
8

Versi Pendek: Anda mungkin juga melakukan pemeriksaan boolean isDebugEnabled ().

Alasan:
1- Jika logika / string string rumit. ditambahkan ke pernyataan debug Anda, Anda sudah memiliki tanda centang di tempat.
2- Anda tidak harus secara selektif memasukkan pernyataan tentang pernyataan debug "kompleks". Semua pernyataan dimasukkan seperti itu.
3- Memanggil log.debug menjalankan perintah berikut sebelum masuk:

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

Ini pada dasarnya sama dengan log panggilan. atau kucing. isDebugEnabled ().

NAMUN! Inilah yang dipikirkan oleh para pengembang log4j (seperti di javadoc mereka dan Anda mungkin harus melakukannya).

Inilah metodenya

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

Ini javadoc untuk itu

/**
*  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.
*   */
Andrew Carr
sumber
1
Terima kasih telah menyertakan JavaDoc. Saya tahu bahwa saya pernah melihat saran ini di suatu tempat sebelumnya dan sedang berusaha menemukan referensi yang pasti. Ini, jika tidak definitif, setidaknya sangat terinformasi.
Simon Peter Chappell
7

Seperti yang disebutkan orang lain menggunakan pernyataan penjaga hanya benar-benar berguna jika membuat string adalah panggilan yang memakan waktu. Contoh spesifik dari ini adalah ketika membuat string akan memicu beberapa pemuatan malas.

Perlu dicatat bahwa masalah ini dapat diselesaikan dengan menggunakan Facade Logging Sederhana untuk Java atau (SLF4J) - http://www.slf4j.org/manual.html . Ini memungkinkan pemanggilan metode seperti:

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

Ini hanya akan mengkonversi parameter yang diteruskan ke string jika debug diaktifkan. SLF4J seperti namanya hanya fasad dan panggilan logging dapat diteruskan ke log4j.

Anda juga dapat dengan mudah "memutar versi Anda sendiri" dari ini.

Semoga ini membantu.

Pablojim
sumber
6

Opsi 2 lebih baik.

Per se itu tidak meningkatkan kinerja. Tapi itu memastikan kinerja tidak menurun. Begini caranya.

Biasanya kami mengharapkan logger.debug (someString);

Tetapi biasanya, seiring pertumbuhan aplikasi, banyak perubahan, terutama pengembang pemula, Anda dapat melihatnya

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

dan sejenisnya.

Bahkan jika level log diatur ke KESALAHAN atau FATAL, rangkaian string dapat terjadi! Jika aplikasi ini berisi banyak pesan tingkat DEBUG dengan rangkaian string, maka tentu dibutuhkan kinerja yang luar biasa terutama dengan jdk 1.4 atau lebih rendah. (Saya tidak yakin apakah nanti versi jdk internall melakukan stringbuffer.append ()).

Itu sebabnya Opsi 2 aman. Bahkan penggabungan string tidak terjadi.

Sathya
sumber
3

Seperti @erickson, itu tergantung. Jika saya ingat, isDebugEnabledsudah membangun debug()metode Log4j.
Selama Anda tidak melakukan beberapa perhitungan mahal dalam pernyataan debug Anda, seperti loop pada objek, melakukan perhitungan dan merangkai string, Anda baik-baik saja menurut saya.

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

akan lebih baik

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());
}
Michael Myers
sumber
3

Untuk satu baris , saya menggunakan bagian dalam pesan logging, dengan cara ini saya tidak melakukan penggabungan:

ej:

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

Saya lakukan:

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

Tetapi untuk beberapa baris kode

ej.

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

Saya lakukan:

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

Karena banyak orang mungkin melihat jawaban ini ketika mencari log4j2 dan hampir semua jawaban saat ini tidak mempertimbangkan log4j2 atau perubahan terbaru di dalamnya, semoga ini akan menjawab pertanyaan.

log4j2 mendukung Pemasok (saat ini implementasi mereka sendiri, tetapi menurut dokumentasi direncanakan untuk menggunakan antarmuka Pemasok Java dalam versi 3.0). Anda dapat membaca sedikit lebih banyak tentang ini di manual . Ini memungkinkan Anda untuk memasukkan pembuatan pesan log yang mahal ke pemasok yang hanya membuat pesan jika akan dicatat:

LogManager.getLogger().debug(() -> createExpensiveLogMessage());
Marcono1234
sumber
2

Ini meningkatkan kecepatan karena itu umum untuk merangkai string dalam teks debug yang mahal misalnya:

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text" + someState);
}
Tom
sumber
1
Jika kita menggunakan jdk 1.5 dan seterusnya maka saya pikir string jadi tidak akan ada bedanya.
Silent Warrior
Bagaimana bisa? Apa yang akan dilakukan JDK5 secara berbeda?
javashlook
1
Dalam jdk 1.5 jika kita menggabungkan string dalam satu pernyataan maka secara internal ia menggunakan metode StringBuffer.append () saja. Jadi itu tidak mempengaruhi kinerja.
Silent Warrior
2
Rangkaian string tidak diragukan lagi membutuhkan waktu. Namun saya tidak yakin saya akan menggambarkannya sebagai 'mahal'. Berapa banyak waktu yang dihemat dalam contoh di atas? Dibandingkan dengan apa yang sebenarnya dilakukan oleh kode di sekitarnya? (mis. pembacaan basis data atau perhitungan dalam memori). Saya pikir pernyataan semacam ini perlu dikualifikasi
31230 Brian Agnew
1
Bahkan JDK 1.4 tidak akan membuat objek String baru dengan penggabungan string sederhana. Penalti kinerja berasal dari penggunaan StringBuffer.append () saat tidak ada string yang ditampilkan sama sekali.
javashlook
1

Karena versi Log4j2.4 (atau slf4j-api 2.0.0-alpha1) jauh lebih baik menggunakan API yang lancar (atau dukungan Java 8 lambda untuk lazy logging ), yang mendukung Supplier<?>argumen pesan log, yang dapat diberikan oleh lambda :

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

ATAU dengan slf4j API:

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

Jika Anda menggunakan opsi 2 Anda melakukan pemeriksaan Boolean yang cepat. Dalam opsi yang Anda lakukan pemanggilan metode (mendorong barang-barang di tumpukan) dan kemudian melakukan pemeriksaan Boolean yang masih cepat. Masalah yang saya lihat adalah konsistensi. Jika beberapa pernyataan debug dan info Anda dibungkus dan beberapa tidak, itu bukan gaya kode yang konsisten. Plus seseorang nanti dapat mengubah pernyataan debug untuk memasukkan string gabungan, yang masih cukup cepat. Saya menemukan bahwa ketika kami menyelesaikan pernyataan debug dan info dalam aplikasi besar dan memprofilkannya, kami menyimpan beberapa poin persentase dalam kinerja. Tidak banyak, tetapi cukup untuk membuatnya sepadan dengan pekerjaan. Saya sekarang memiliki beberapa pengaturan makro di IntelliJ untuk secara otomatis menghasilkan debug debug dan pernyataan info untuk saya.

Javamann
sumber
0

Saya akan merekomendasikan menggunakan Opsi 2 sebagai de facto untuk sebagian besar karena tidak super mahal.

Kasus 1: log.debug ("satu string")

Case2: log.debug ("satu string" + "dua string" + object.toString + object2.toString)

Pada saat salah satu dari ini disebut, string parameter dalam log.debug (baik itu KASUS 1 atau Case2) HARUS dievaluasi. Itulah yang dimaksud semua orang dengan 'mahal.' Jika Anda memiliki kondisi sebelumnya, 'isDebugEnabled ()', ini tidak harus dievaluasi di mana kinerja disimpan.

Jolly1234
sumber
0

Pada 2.x, Apache Log4j memiliki pemeriksaan ini bawaan, jadi isDebugEnabled()tidak perlu lagi. Lakukan saja debug()dan pesan akan ditekan jika tidak diaktifkan.

ha9u63ar
sumber
-1

Log4j2 memungkinkan Anda memformat parameter ke dalam templat pesan, mirip dengan String.format(), sehingga tidak perlu lagi dilakukan isDebugEnabled().

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

Contoh log4j2.properties sederhana:

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
Andre
sumber