Untuk pertama kalinya dalam hidup saya, saya menemukan diri saya dalam posisi di mana saya sedang menulis Java API yang akan bersumber terbuka. Semoga bisa dimasukkan dalam banyak proyek lainnya.
Untuk logging saya (dan memang orang yang bekerja dengan saya) selalu menggunakan JUL (java.util.logging) dan tidak pernah memiliki masalah dengan itu. Namun sekarang saya perlu memahami secara lebih rinci apa yang harus saya lakukan untuk pengembangan API saya. Saya telah melakukan beberapa penelitian tentang ini dan dengan informasi yang saya dapatkan saya menjadi semakin bingung. Karenanya pos ini.
Karena saya berasal dari JUL, saya bias dalam hal itu. Pengetahuan saya tentang sisanya tidak sebesar itu.
Dari penelitian yang saya lakukan, saya menemukan alasan mengapa orang tidak suka JUL:
"Saya mulai berkembang di Jawa jauh sebelum Sun merilis JUL dan lebih mudah bagi saya untuk melanjutkan logging-framework-X daripada belajar sesuatu yang baru" . Hmm. Saya tidak bercanda, ini sebenarnya yang dikatakan orang. Dengan argumen ini kita semua bisa melakukan COBOL. (Namun saya pasti bisa menghubungkan ini menjadi cowok malas sendiri)
"Saya tidak suka nama level logging di JUL" . Ok, serius, ini tidak cukup alasan untuk memperkenalkan ketergantungan baru.
"Saya tidak suka format standar keluaran dari JUL" . Hmm. Ini hanya konfigurasi. Anda bahkan tidak perlu melakukan apapun secara bijaksana. (benar, di masa lalu Anda mungkin harus membuat kelas Formatter Anda sendiri untuk memperbaikinya).
"Saya menggunakan perpustakaan lain yang juga menggunakan logging-framework-X jadi saya pikir lebih mudah hanya menggunakan yang itu" . Ini argumen melingkar, bukan? Mengapa 'semua orang' menggunakan logging-framework-X dan bukan JULI?
"Semua orang menggunakan logging-framework-X" . Ini bagi saya hanyalah kasus khusus di atas. Mayoritas tidak selalu benar.
Jadi pertanyaan besar sebenarnya adalah mengapa tidak JULI? . Apa yang saya lewatkan? The raison d'être untuk fasad logging (SLF4J, JCL) adalah bahwa beberapa implementasi logging telah ada secara historis dan alasan untuk itu benar-benar kembali ke era sebelum JUL seperti yang saya lihat. Jika JUL sempurna maka fasad logging tidak akan ada, atau apa? Untuk membuat hal-hal yang lebih membingungkan, JUL adalah suatu fasad itu sendiri, memungkinkan Handler, Formatters dan bahkan LogManager untuk ditukar.
Daripada merangkul banyak cara untuk melakukan hal yang sama (logging), bukankah kita seharusnya mempertanyakan mengapa mereka diperlukan? (dan lihat apakah alasan itu masih ada)
Ok, penelitian saya sejauh ini telah mengarah pada beberapa hal yang dapat saya lihat mungkin masalah nyata dengan JUL:
Performa . Ada yang mengatakan bahwa kinerja di SLF4J lebih unggul dari yang lain. Bagi saya ini adalah optimasi yang prematur. Jika Anda perlu mencatat ratusan megabyte per detik maka saya tidak yakin Anda berada di jalur yang benar. JUL juga telah berevolusi dan tes yang Anda lakukan di Java 1.4 mungkin tidak lagi benar. Anda dapat membacanya di sini dan perbaikan ini telah membuatnya menjadi Java 7. Banyak juga yang berbicara tentang overhead dari penggabungan string dalam metode logging. Namun logging berbasis template menghindari biaya ini dan ada juga di JUL. Secara pribadi saya tidak pernah benar-benar menulis logging berbasis template. Terlalu malas untuk itu. Sebagai contoh jika saya melakukan ini dengan JULI:
log.finest("Lookup request from username=" + username + ", valueX=" + valueX + ", valueY=" + valueY));
IDE saya akan memperingatkan saya dan meminta izin untuk mengubahnya menjadi:
log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}", new Object[]{username, valueX, valueY});
.. yang tentu saja akan saya terima. Izin diberikan ! Terima kasih untuk bantuannya.
Jadi saya tidak benar-benar menulis pernyataan seperti itu sendiri, yang dilakukan oleh IDE.
Kesimpulannya pada masalah kinerja saya belum menemukan apa pun yang menunjukkan bahwa kinerja JUL tidak ok dibandingkan dengan kompetisi.
Konfigurasi dari classpath . JUL out-of-the-box tidak dapat memuat file konfigurasi dari classpath. Ini adalah beberapa baris kode untuk membuatnya melakukannya. Saya bisa melihat mengapa ini mungkin mengganggu tetapi solusinya pendek dan sederhana.
Ketersediaan penangan keluaran . JUL hadir dengan 5 penangan keluaran out-of-the-box: konsol, aliran file, soket dan memori. Ini dapat diperpanjang atau yang baru dapat ditulis. Misalnya, ini dapat ditulis ke UNIX / Linux Syslog dan Windows Event Log. Saya pribadi tidak pernah memiliki persyaratan ini atau saya melihatnya digunakan tetapi saya pasti bisa menghubungkan mengapa itu mungkin fitur yang berguna. Logback dilengkapi dengan appender untuk Syslog misalnya. Masih saya akan membantah itu
- 99,5% dari kebutuhan untuk destinasi keluaran dicakup oleh apa yang ada di JUL out-of-the-box.
- Kebutuhan khusus dapat dipenuhi oleh penangan khusus di atas JUL daripada di atas sesuatu yang lain. Tidak ada bagi saya yang menunjukkan bahwa dibutuhkan lebih banyak waktu untuk menulis handler keluaran Syslog untuk JUL daripada yang dilakukan untuk kerangka kerja logging lain.
Saya benar-benar khawatir ada sesuatu yang saya abaikan. Penggunaan fasad penebangan dan implementasi penebangan selain JUL sangat luas sehingga saya harus sampai pada kesimpulan bahwa saya yang tidak mengerti. Itu bukan pertama kalinya, saya khawatir. :-)
Jadi apa yang harus saya lakukan dengan API saya? Saya ingin menjadi sukses. Tentu saja saya bisa hanya "mengikuti arus" dan menerapkan SLF4J (yang tampaknya paling populer akhir-akhir ini) tetapi demi saya sendiri, saya masih perlu memahami persis apa yang salah dengan JULI hari ini yang menjamin semua fuzz? Apakah saya akan menyabot diri dengan memilih JUL untuk perpustakaan saya?
Menguji kinerja
(bagian ditambahkan oleh nolan600 pada 07-JUL-2012)
Ada referensi di bawah ini dari Ceki tentang parametri SLF4 menjadi 10 kali atau lebih cepat daripada JUL. Jadi saya sudah mulai melakukan beberapa tes sederhana. Sepintas klaim itu tentu benar. Berikut adalah hasil awal (tapi baca terus!):
- Waktu pelaksanaan SLF4J, backend Logback: 1515
- Waktu eksekusi SLF4J, backend JUL: 12938
- Waktu eksekusi JUL: 16911
Angka-angka di atas adalah msec jadi lebih sedikit lebih baik. Jadi perbedaan kinerja 10 kali dengan yang pertama sebenarnya cukup dekat. Reaksi awal saya: Banyak sekali!
Inilah inti dari tes ini. Seperti dapat dilihat integer dan string dibangun dalam satu loop yang kemudian digunakan dalam pernyataan log:
for (int i = 0; i < noOfExecutions; i++) {
for (char x=32; x<88; x++) {
String someString = Character.toString(x);
// here we log
}
}
(Saya ingin pernyataan log memiliki tipe data primitif (dalam hal ini int) dan tipe data yang lebih kompleks (dalam hal ini sebuah String). Tidak yakin itu penting tetapi Anda memilikinya.)
Pernyataan log untuk SLF4J:
logger.info("Logging {} and {} ", i, someString);
Pernyataan log untuk JUL:
logger.log(Level.INFO, "Logging {0} and {1}", new Object[]{i, someString});
JVM 'menghangat' dengan tes yang sama dilakukan sekali sebelum pengukuran yang sebenarnya dilakukan. Java 1.7.03 digunakan pada Windows 7. Versi terbaru SLF4J (v1.6.6) dan Logback (v1.0.6) digunakan. Stdout dan stderr dialihkan ke perangkat nol.
Namun, hati-hati sekarang, ternyata JUL menghabiskan sebagian besar waktunya getSourceClassName()
karena JUL secara default mencetak nama kelas sumber dalam output, sementara Logback tidak. Jadi kami membandingkan apel dan jeruk. Saya harus melakukan tes lagi dan mengkonfigurasi implementasi logging dengan cara yang sama sehingga mereka benar-benar menghasilkan hal yang sama. Namun saya menduga bahwa SLF4J + Logback masih akan keluar di atas tetapi jauh dari angka awal seperti yang diberikan di atas. Tetap disini.
Btw: Tes ini pertama kali saya benar-benar bekerja dengan SLF4J atau Logback. Pengalaman yang menyenangkan. JUL tentu jauh lebih tidak ramah ketika Anda memulai.
Menguji kinerja (bagian 2)
(bagian ditambahkan oleh nolan600 pada 08-JUL-2012)
Ternyata tidak masalah untuk kinerja bagaimana Anda mengkonfigurasi pola Anda di JUL, yaitu apakah itu termasuk nama sumber atau tidak. Saya mencoba dengan pola yang sangat sederhana:
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
dan itu tidak mengubah timing di atas sama sekali. Profiler saya mengungkapkan bahwa logger masih menghabiskan banyak waktu dalam panggilan getSourceClassName()
bahkan jika ini bukan bagian dari pola saya. Polanya tidak masalah.
Oleh karena itu saya menyimpulkan pada masalah kinerja bahwa setidaknya untuk pernyataan log berbasis template yang diuji tampaknya ada kira-kira faktor 10 dalam perbedaan kinerja nyata antara JUL (lambat) dan SLF4J + Logback (cepat). Seperti kata Ceki.
Saya juga bisa melihat hal lain yaitu bahwa getLogger()
panggilan SLF4J jauh lebih mahal daripada JUL. (95 ms vs 0,3 ms jika profiler saya akurat). Ini masuk akal. SLF4J harus melakukan beberapa waktu untuk mengikat implementasi logging yang mendasarinya. Ini tidak membuatku takut. Panggilan ini harus agak jarang terjadi dalam masa aplikasi. Tahan luntur harus dalam panggilan log yang sebenarnya.
Kesimpulan akhir
(bagian ditambahkan oleh nolan600 pada 08-JUL-2012)
Terima kasih atas semua jawaban Anda. Bertentangan dengan apa yang awalnya saya pikir saya akhirnya memutuskan untuk menggunakan SLF4J untuk API saya. Ini didasarkan pada sejumlah hal dan masukan Anda:
Ini memberikan fleksibilitas untuk memilih implementasi log pada waktu penyebaran.
Masalah dengan kurangnya fleksibilitas konfigurasi JUL saat dijalankan di dalam server aplikasi.
SLF4J tentu jauh lebih cepat seperti yang dijelaskan di atas khususnya jika Anda memasangkannya dengan Logback. Bahkan jika ini hanya tes kasar saya punya alasan untuk percaya bahwa lebih banyak upaya telah dilakukan untuk optimasi pada SLF4J + Logback daripada pada JUL.
Dokumentasi. Dokumentasi untuk SLF4J jauh lebih komprehensif dan tepat.
Fleksibilitas pola. Ketika saya melakukan tes saya menetapkan untuk JUL meniru pola default dari Logback. Pola ini termasuk nama utas. Ternyata JULI tidak bisa melakukan ini di luar kotak. Ok, saya belum melewatkannya sampai sekarang, tetapi saya tidak berpikir itu adalah hal yang harus hilang dari kerangka log. Titik!
Sebagian besar (atau banyak) proyek Java saat ini menggunakan Maven sehingga menambahkan ketergantungan bukanlah hal yang besar terutama jika ketergantungan itu agak stabil, yaitu tidak selalu mengubah API-nya. Ini tampaknya berlaku untuk SLF4J. Gelas dan teman SLF4J juga berukuran kecil.
Jadi hal aneh yang terjadi adalah saya benar-benar kesal dengan JUL setelah bekerja sedikit dengan SLF4J. Saya masih menyesal bahwa ini harus dengan JUL. Juli memang jauh dari sempurna, tetapi jenis pekerjaan yang dilakukannya. Tidak cukup baik. Hal yang sama dapat dikatakan tentang Properties
sebagai contoh tetapi kami tidak berpikir tentang abstrak sehingga orang dapat menyambungkan pustaka konfigurasi mereka sendiri dan apa pun yang Anda miliki. Saya pikir alasannya adalah yang Properties
datang tepat di atas bar sementara yang sebaliknya berlaku untuk JUL hari ini ... dan di masa lalu itu datang pada nol karena tidak ada.
java.lang.System.Logger
, yang merupakan antarmuka , yang dapat diarahkan ke kerangka kerja logging aktual apa pun yang Anda inginkan, selama kerangka itu berhasil dan menyediakan implementasi antarmuka itu. Dikombinasikan dengan modularisasi, Anda bahkan bisa menggunakan aplikasi dengan JRE yang dibundel tidak berisi java.util.logging
, jika Anda lebih suka kerangka kerja yang berbeda.