Apa cara yang benar untuk menangani hasil debug di Jawa?

32

Ketika proyek Java saya saat ini tumbuh lebih besar dan lebih besar, saya merasakan kebutuhan yang semakin besar untuk memasukkan hasil debug di beberapa titik kode saya.

Untuk mengaktifkan atau menonaktifkan fitur ini dengan tepat, tergantung pada pembukaan atau penutupan sesi tes, saya biasanya meletakkan a private static final boolean DEBUG = falsedi awal kelas yang diperiksa oleh tes saya, dan menggunakannya dengan sepele seperti ini (misalnya):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

  public void myMethod(String s) {
    if (DEBUG) {
      System.out.println(s);
    }
  }
}

dan sejenisnya.

Tapi itu tidak membuat saya senang, karena tentu saja itu berhasil tetapi mungkin ada terlalu banyak kelas untuk mengatur DEBUG menjadi benar, jika Anda tidak hanya menatap beberapa dari mereka.

Sebaliknya, saya (seperti - saya pikir - banyak lainnya) tidak suka menempatkan seluruh aplikasi dalam mode debug, karena jumlah teks yang dihasilkan bisa sangat besar.

Jadi, apakah ada cara yang tepat untuk menangani situasi seperti itu secara arsitektur atau cara yang paling benar adalah dengan menggunakan anggota kelas DEBUG?

Federico Zancan
sumber
14
di Jawa, cara yang benar BUKAN menggunakan kode homebrew untuk login. Pilih kerangka kerja yang sudah mapan, jangan menemukan kembali roda
agas
Saya menggunakan DEBUG boolean di beberapa kelas saya yang lebih rumit, untuk alasan yang sama seperti yang Anda nyatakan. Saya biasanya tidak ingin men-debug seluruh aplikasi, hanya kelas yang memberi saya masalah. Kebiasaan itu berasal dari hari-hari COBOL saya, di mana pernyataan DISPLAY adalah satu-satunya bentuk debugging yang tersedia.
Gilbert Le Blanc
1
Saya juga akan merekomendasikan lebih mengandalkan debugger bila mungkin dan tidak membuang sampah sembarangan kode Anda dengan pernyataan debug.
Andrew T Finnell
1
Apakah Anda berlatih Test Driven Development (TDD) dengan unit test? Begitu saya mulai melakukan itu, saya melihat pengurangan besar dalam 'kode debug'.
JW01

Jawaban:

52

Anda ingin melihat kerangka kerja logging, dan mungkin pada kerangka kerja fasad logging.

Ada beberapa kerangka kerja penebangan di luar sana, seringkali dengan fungsi yang tumpang tindih, sedemikian rupa sehingga seiring berjalannya waktu banyak yang berevolusi untuk bergantung pada API umum, atau telah digunakan melalui kerangka fasad untuk mengabstraksi penggunaannya dan memungkinkannya untuk ditukar di tempat. jika diperlukan.

Kerangka kerja

Beberapa Kerangka Penebangan

Beberapa Fasad Penebangan

Pemakaian

Contoh dasar

Sebagian besar kerangka kerja ini akan memungkinkan Anda untuk menulis sesuatu dari formulir (di sini menggunakan slf4j-apidan logback-core):

package chapters.introduction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// copied from: http://www.slf4j.org/manual.html
public class HelloWorld {

  public static void main(String[] args) {
    final Logger logger = LoggerFactory.getLogger(HelloWorld.class);

    logger.debug("Hello world, I'm a DEBUG level message");
    logger.info("Hello world, I'm an INFO level message");
    logger.warn("Hello world, I'm a WARNING level message");
    logger.error("Hello world, I'm an ERROR level message");
  }
}

Perhatikan penggunaan kelas saat ini untuk membuat logger khusus, yang akan memungkinkan SLF4J / LogBack untuk memformat output dan menunjukkan dari mana pesan logging berasal.

Seperti dicatat dalam manual SLF4J , pola penggunaan khas di kelas biasanya:

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  

public class MyClass {

    final Logger logger = LoggerFactory.getLogger(MyCLASS.class);

    public void doSomething() {
        // some code here
        logger.debug("this is useful");

        if (isSomeConditionTrue()) {
            logger.info("I entered by conditional block!");
        }
    }
}

Tetapi pada kenyataannya, bahkan lebih umum untuk mendeklarasikan logger dengan formulir:

private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);

Ini memungkinkan logger untuk digunakan dari dalam metode statis juga, dan itu dibagi di antara semua instance kelas. Ini kemungkinan besar adalah bentuk yang Anda sukai. Namun, seperti dicatat oleh Brendan Long dalam komentar, Anda ingin memastikan untuk memahami implikasinya dan memutuskannya (ini berlaku untuk semua kerangka kerja penebangan yang mengikuti idiom-idiom ini).

Ada beberapa cara lain untuk membuat instance logger, misalnya dengan menggunakan parameter string untuk membuat nama logger:

Logger logger = LoggerFactory.getLogger("MyModuleName");

Level Debug

Tingkat debug bervariasi dari satu kerangka kerja ke kerangka kerja lainnya, tetapi yang umum adalah (dalam urutan kritikalitas, dari jinak ke buruk-buruk, dan dari yang sangat umum hingga mudah-mudahan sangat jarang):

  • TRACE Informasi yang sangat rinci. Harus ditulis hanya untuk log. Digunakan hanya untuk melacak aliran program di pos-pos pemeriksaan.

  • DEBUG Informasi rinci. Harus ditulis hanya untuk log.

  • INFO Acara runtime yang terkenal. Harus segera terlihat di konsol, jadi gunakan hemat.

  • WARNING Keanehan runtime dan kesalahan yang dapat dipulihkan.

  • ERROR Kesalahan runtime lain atau kondisi yang tidak terduga.

  • FATAL Kesalahan parah yang menyebabkan penghentian prematur.

Blok dan Pelindung

Sekarang, katakan Anda memiliki bagian kode di mana Anda akan menulis sejumlah pernyataan debug. Ini dapat dengan cepat memengaruhi kinerja Anda, baik karena dampak dari pencatatan itu sendiri maupun dari pembuatan parameter apa pun yang mungkin Anda lewati ke metode pencatatan.

Untuk menghindari masalah seperti ini, Anda sering ingin menulis sesuatu dari formulir:

if (LOGGER.isDebugEnabled()) {
   // lots of debug logging here, or even code that
   // is only used in a debugging context.
   LOGGER.debug(" result: " + heavyComputation());
}

Jika Anda belum pernah menggunakan pelindung ini sebelum blok pernyataan debug, meskipun pesan mungkin tidak dihasilkan (jika, misalnya, logger Anda saat ini dikonfigurasikan untuk mencetak hanya hal-hal di atas INFOlevel), heavyComputation()metode ini masih akan dieksekusi .

Konfigurasi

Konfigurasi sangat tergantung pada kerangka logging Anda, tetapi mereka menawarkan sebagian besar teknik yang sama untuk ini:

  • konfigurasi terprogram (saat runtime, melalui API - memungkinkan untuk perubahan runtime ),
  • konfigurasi deklaratif statis (saat start-time, biasanya melalui file XML atau properti - kemungkinan merupakan yang Anda butuhkan pada awalnya ).

Mereka juga menawarkan sebagian besar kemampuan yang sama:

  • konfigurasi format pesan keluaran (cap waktu, spidol, dll ...),
  • konfigurasi level output,
  • konfigurasi filter berbutir halus (misalnya untuk menyertakan / mengecualikan paket atau kelas),
  • konfigurasi appenders untuk menentukan di mana harus log (untuk menghibur, untuk file, ke layanan web ...) dan mungkin apa yang harus dilakukan dengan log yang lebih lama (misalnya, dengan file bergulir otomatis).

Berikut adalah contoh umum dari konfigurasi deklaratif, menggunakan logback.xmlfile.

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Seperti yang disebutkan, ini tergantung pada kerangka kerja Anda dan mungkin ada alternatif lain (misalnya, LogBack juga memungkinkan skrip Groovy untuk digunakan). Format konfigurasi XML juga dapat bervariasi dari satu implementasi ke yang lain.

Untuk contoh konfigurasi lainnya, silakan rujuk (antara lain) ke:

Beberapa Kesenangan Sejarah

Harap dicatat bahwa Log4J adalah melihat update besar pada saat ini, transisi dari versi 1.x ke 2.x . Anda mungkin ingin melihat keduanya untuk kesenangan atau kebingungan yang lebih historis, dan jika Anda memilih Log4J mungkin lebih suka menggunakan versi 2.x.

Perlu dicatat, seperti yang disebutkan Mike Partridge dalam komentar, bahwa LogBack dibuat oleh mantan anggota tim Log4J. Yang dibuat untuk mengatasi kekurangan kerangka Java Logging. Dan bahwa versi utama Log4J 2.x mendatang adalah dirinya sendiri sekarang mengintegrasikan beberapa fitur yang diambil dari LogBack.

Rekomendasi

Intinya, tetap terpisah sebanyak yang Anda bisa, bermain-main dengan beberapa, dan lihat apa yang terbaik untuk Anda. Pada akhirnya itu hanya kerangka kerja logging . Kecuali jika Anda memiliki alasan yang sangat spesifik, terlepas dari kemudahan penggunaan dan preferensi pribadi, semua ini akan lebih baik sehingga tidak ada gunanya digantung. Sebagian besar dari mereka juga dapat diperluas sesuai kebutuhan Anda.

Namun, jika saya harus memilih kombinasi hari ini, saya akan menggunakan LogBack + SLF4J. Tetapi jika Anda bertanya kepada saya beberapa tahun kemudian saya akan merekomendasikan Log4J dengan Apache Commons Logging, jadi perhatikan dependensi Anda dan berevolusi bersama mereka.

haylem
sumber
1
SLF4J dan LogBack ditulis oleh orang yang awalnya menulis Log4J.
Mike Partridge
4
Bagi mereka yang mungkin khawatir tentang dampak kinerja dari logging: slf4j.org/faq.html#logging_performance
Mike Partridge
2
Perlu disebutkan bahwa tidak begitu jelas apakah Anda harus membuat penebang static, karena menghemat sedikit memori, tetapi menyebabkan masalah dalam kasus-kasus tertentu: slf4j.org/faq.html#declared_static
Monica
1
@ MikePartridge: Saya mengetahui konten tautan, tetapi masih tidak akan mencegah evaluasi parameter misalnya. alasan penebangan parameterized byt lebih performant adalah karena pemrosesan pesan log tidak akan terjadi (string terutama concatenation). Namun, setiap pemanggilan metode akan dieksekusi jika dilewatkan sebagai parameter. Jadi, tergantung pada kasus penggunaan Anda, blok dapat berguna. Dan seperti yang disebutkan dalam posting, mereka juga dapat berguna bagi Anda hanya untuk mengelompokkan aktivitas terkait debug lainnya (bukan hanya mencatat) yang terjadi ketika tingkat DEBUG diaktifkan.
haylem
1
@Haylem - itu benar, kesalahan saya.
Mike Partridge
2

gunakan framework logging

sebagian besar waktu ada metode pabrik statis

private static final Logger logger = Logger.create("classname");

maka Anda dapat menampilkan kode logging dengan level berbeda:

logger.warning("error message");
logger.info("informational message");
logger.trace("detailed message");

maka akan ada satu file di mana Anda dapat menentukan pesan mana untuk setiap kelas yang harus ditulis ke output log (file atau stderr)

ratchet freak
sumber
1

Inilah yang dimaksud dengan kerangka logging seperti log4j atau slf4j yang lebih baru. Mereka memungkinkan Anda untuk mengontrol pendataan dengan sangat rinci, dan mengonfigurasinya bahkan saat aplikasi sedang berjalan.

Michael Borgwardt
sumber
0

Kerangka kerja logging jelas merupakan jalan yang harus ditempuh. Namun, Anda juga harus memiliki test suite yang baik. Cakupan pengujian yang baik sering kali menghilangkan kebutuhan untuk debug output secara bersamaan.

Dima
sumber
Jika Anda menggunakan kerangka logging dan memiliki debug logging tersedia - akan tiba saatnya ini akan menyelamatkan Anda dari mengalami hari yang benar-benar buruk.
Fortyrunner
1
Saya tidak mengatakan Anda tidak harus login. Saya bilang Anda harus melakukan tes terlebih dahulu.
Dima