วิธีที่ถูกต้องในการจัดการกับการแก้ปัญหาการส่งออกใน Java คืออะไร


32

ในขณะที่โครงการ Java ปัจจุบันของฉันเติบโตขึ้นเรื่อย ๆ ฉันรู้สึกว่าจำเป็นที่จะต้องเพิ่มการดีบักเอาต์พุตในหลาย ๆ จุดของโค้ดของฉัน

หากต้องการเปิดใช้งานหรือปิดใช้งานคุณลักษณะนี้อย่างเหมาะสมขึ้นอยู่กับการเปิดหรือปิดเซสชันการทดสอบฉันมักจะใส่private static final boolean DEBUG = falseจุดเริ่มต้นของชั้นเรียนที่การทดสอบของฉันกำลังตรวจสอบอยู่และใช้วิธีนี้เล็กน้อย (เช่น):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

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

และไม่ชอบ

แต่นั่นไม่ได้ทำให้ฉันสุขใจเพราะแน่นอนว่ามันใช้ได้ แต่อาจมีหลายคลาสเกินไปที่จะตั้งค่า DEBUG ให้เป็นจริงถ้าคุณไม่จ้องมองเพียงสองสามคน

ในทางกลับกันฉัน (เหมือน - ฉันคิดว่า - คนอื่น ๆ ) ไม่ชอบที่จะใส่แอปพลิเคชันทั้งหมดในโหมดแก้ไขข้อบกพร่องเนื่องจากปริมาณข้อความที่ส่งออกอาจล้นหลาม

ดังนั้นมีวิธีที่ถูกต้องในการจัดการกับสถานการณ์ดังกล่าวทางสถาปัตยกรรมหรือวิธีที่ถูกต้องที่สุดคือการใช้สมาชิกของคลาส DEBUG หรือไม่


14
ใน Java วิธีที่ถูกต้องคือไม่ใช้รหัส homebrew สำหรับการบันทึก เลือกกรอบที่ยอมรับไม่ได้บูรณาการล้อ
ริ้น

ฉันใช้บูลีน DEBUG ในคลาสที่ซับซ้อนของฉันด้วยเหตุผลเดียวกับที่คุณระบุ ปกติฉันไม่ต้องการที่จะดีบั๊กแอปพลิเคชันทั้งหมดเพียงชั้นเรียนให้ฉันมีปัญหา นิสัยมาจากวันภาษาโคบอลของฉันที่ซึ่งคำสั่ง DISPLAY เป็นรูปแบบการดีบักเพียงอย่างเดียว
Gilbert Le Blanc

1
ฉันขอแนะนำให้ใช้โปรแกรมดีบักเพิ่มเติมเมื่อเป็นไปได้และอย่าทิ้งรหัสของคุณด้วยคำสั่ง debug
Andrew T Finnell

1
คุณฝึกการพัฒนาระบบขับเคลื่อนทดสอบ (TDD) ด้วยการทดสอบหน่วยหรือไม่? เมื่อฉันเริ่มทำเช่นนั้นฉันสังเกตเห็นการลดลงอย่างมากใน 'รหัสการดีบัก'
JW01

คำตอบ:


52

คุณต้องการดูเฟรมเวิร์กการบันทึกและอาจเป็นเฟรมเวิร์กซุ้มการบันทึก

มีเฟรมเวิร์กการบันทึกข้อมูลหลายเฟรมที่มักจะมีฟังก์ชันการทับซ้อนกันมากดังนั้นเมื่อเวลาผ่านไปหลายคนจึงต้องพึ่งพา API ทั่วไปหรือมีการใช้งานผ่านเฟรมเวิร์กของซุ้มเพื่อสรุปการใช้งานและอนุญาตให้สลับ หากมีความจำเป็น.

กรอบ

กรอบการบันทึกบางอย่าง

  • Java Logging Framework (ส่วนหนึ่งของ JDK)
  • Apache Log4J (ค่อนข้างเก่า แต่ยังคงแข็งแกร่งและได้รับการบำรุงรักษาอย่างแข็งขัน)
  • LogBack (สร้างขึ้นเพื่อให้วิธีการที่ทันสมัยกว่า Log4J โดยหนึ่งในผู้สร้างของLog4J )

อาคารที่บันทึกไว้บางส่วน

  • SLF4J (โดยผู้สร้างLogBackแต่มีอะแดปเตอร์สำหรับกรอบอื่น ๆ )
  • Apache Commons Logging (แต่ลงวันที่เล็กน้อยตอนนี้)

การใช้

ตัวอย่างพื้นฐาน

เฟรมเวิร์กเหล่านี้ส่วนใหญ่จะอนุญาตให้คุณเขียนบางสิ่งในแบบฟอร์ม (ที่นี่ใช้slf4j-apiและ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");
  }
}

สังเกตการใช้คลาสปัจจุบันเพื่อสร้างตัวบันทึกเฉพาะซึ่งจะอนุญาตให้ SLF4J / LogBack จัดรูปแบบเอาต์พุตและระบุตำแหน่งที่ข้อความการบันทึกมาจาก

ดังที่ระบุไว้ในคู่มือ SLF4Jรูปแบบการใช้งานทั่วไปในคลาสนั้นมักจะ:

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!");
        }
    }
}

แต่ในความเป็นจริงมันเป็นเรื่องธรรมดามากที่จะประกาศตัวบันทึกด้วยแบบฟอร์ม:

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

สิ่งนี้อนุญาตให้ใช้ตัวบันทึกจากภายในวิธีการแบบคงที่เช่นกันและจะใช้ร่วมกันระหว่างอินสแตนซ์ทั้งหมดของคลาส นี่เป็นแบบฟอร์มที่คุณต้องการ อย่างไรก็ตามตามที่เบรนแดนลองระบุไว้ในความคิดเห็นคุณต้องการให้แน่ใจว่าได้เข้าใจความหมายและตัดสินใจได้อย่างถูกต้อง (สิ่งนี้ใช้กับกรอบการบันทึกทั้งหมดตามหลังสำนวนเหล่านี้)

มีวิธีอื่นในการสร้างอินสแตนซ์ตัวบันทึกตัวอย่างเช่นโดยใช้พารามิเตอร์สตริงเพื่อสร้างตัวบันทึกชื่อ:

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

แก้ไขระดับ

ระดับการดีบักแตกต่างจากกรอบงานหนึ่งไปอีกกรอบหนึ่ง แต่ระดับทั่วไปคือ (เรียงตามลำดับความสำคัญจากความอ่อนโยนและไม่ดีต่อค้างคาวและอาจเป็นเรื่องธรรมดามากที่จะหายากมาก):

  • TRACE ข้อมูลรายละเอียดมาก ควรเขียนลงในบันทึกเท่านั้น ใช้เพื่อติดตามโฟลว์ของโปรแกรมที่จุดตรวจเท่านั้น

  • DEBUG รายละเอียดข้อมูล. ควรเขียนลงในบันทึกเท่านั้น

  • INFO เหตุการณ์รันไทม์ที่สังเกตเห็นได้ ควรมองเห็นได้ทันทีบนคอนโซลดังนั้นใช้อย่าง จำกัด

  • WARNING Runtime แปลกประหลาดและข้อผิดพลาดที่กู้คืนได้

  • ERROR ข้อผิดพลาดรันไทม์อื่น ๆ หรือเงื่อนไขที่ไม่คาดคิด

  • FATAL ข้อผิดพลาดรุนแรงที่ทำให้เกิดการยกเลิกก่อนกำหนด

บล็อกและยาม

ตอนนี้สมมติว่าคุณมีส่วนของรหัสที่คุณกำลังจะเขียนคำสั่ง debug จำนวนหนึ่ง สิ่งนี้อาจส่งผลกระทบต่อประสิทธิภาพการทำงานของคุณอย่างรวดเร็วเนื่องจากทั้งผลกระทบของการบันทึกและการสร้างพารามิเตอร์ใด ๆ ที่คุณอาจส่งผ่านไปยังวิธีการบันทึก

เพื่อหลีกเลี่ยงปัญหาประเภทนี้คุณมักจะต้องการเขียนอะไรบางอย่างของแบบฟอร์ม:

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

หากคุณไม่ได้ใช้การ์ดป้องกันนี้ก่อนที่จะบล็อกคำสั่ง debug แม้ว่าข้อความอาจไม่ได้รับการส่งออก (ตัวอย่างเช่นในปัจจุบัน logger ของคุณได้รับการกำหนดค่าให้พิมพ์เฉพาะสิ่งที่เหนือINFOระดับ)heavyComputation()วิธีการดังกล่าวจะยังคงดำเนินการอยู่ .

องค์ประกอบ

การกำหนดค่าค่อนข้างขึ้นอยู่กับกรอบการบันทึกของคุณ แต่ส่วนใหญ่จะนำเสนอเทคนิคที่เหมือนกันสำหรับสิ่งนี้:

  • การกำหนดค่าแบบเป็นโปรแกรม (ที่รันไทม์ผ่าน API - อนุญาตสำหรับการเปลี่ยนแปลงรันไทม์ ),
  • การกำหนดค่าประกาศคงที่ (ณ เวลาเริ่มต้นมักจะผ่านทาง XML หรือไฟล์คุณสมบัติ - น่าจะเป็นสิ่งที่คุณต้องการในตอนแรก )

พวกเขายังมีความสามารถส่วนใหญ่เหมือนกัน:

  • การกำหนดค่าของรูปแบบข้อความที่ส่งออก (การประทับเวลาเครื่องหมาย ฯลฯ ... )
  • การกำหนดค่าของระดับเอาท์พุท
  • การกำหนดค่าของตัวกรองละเอียด (ตัวอย่างเช่นเพื่อรวม / ไม่รวมแพ็คเกจหรือคลาส)
  • การกำหนดค่าของ appenders เพื่อกำหนดตำแหน่งที่จะบันทึก (ไปยังคอนโซลไปยังไฟล์ไปยังบริการบนเว็บ ... ) และอาจจะทำอย่างไรกับบันทึกเก่า ๆ (ตัวอย่างเช่นกับไฟล์กลิ้งอัตโนมัติ)

นี่คือตัวอย่างทั่วไปของการกำหนดค่าที่ประกาศโดยใช้logback.xmlไฟล์

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

ตามที่กล่าวมานี้ขึ้นอยู่กับกรอบงานของคุณและอาจมีทางเลือกอื่น ๆ (เช่น LogBack ยังอนุญาตให้ใช้ Groovy script) รูปแบบการกำหนดค่า XML อาจแตกต่างจากการใช้งานหนึ่งไปยังอีก

สำหรับตัวอย่างการกำหนดค่าเพิ่มเติมโปรดดู (ในหมู่อื่น ๆ ) เพื่อ:

สนุกกับประวัติศาสตร์

โปรดทราบว่าLog4Jจะเห็นการปรับปรุงที่สำคัญในขณะนี้เปลี่ยนจากรุ่น1.xเพื่อ2.x คุณอาจต้องการดูทั้งสองอย่างเพื่อความสนุกสนานในอดีตหรือความสับสนและหากคุณเลือกLog4Jอาจต้องการไปกับรุ่น 2.x

เป็นที่น่าสังเกตว่า Mike Partridge ได้กล่าวถึงความคิดเห็นไว้ว่า LogBack นั้นถูกสร้างขึ้นโดยอดีตสมาชิกทีม Log4J ซึ่งถูกสร้างขึ้นเพื่อแก้ไขข้อบกพร่องของเฟรมเวิร์ก Java Logging และที่สำคัญรุ่น Log4J 2.x ตอนนี้กำลังรวมคุณลักษณะบางอย่างที่นำมาจาก LogBack

คำแนะนำ

ที่บรรทัดล่างพัก decoupled ให้มากที่สุดเท่าที่จะทำได้เล่นกับคนรอบข้างและดูว่าอะไรดีที่สุดสำหรับคุณ ในท้ายที่สุดมันก็เป็นเพียงกรอบการเข้าสู่ระบบ ยกเว้นถ้าคุณมีเหตุผลที่เฉพาะเจาะจงนอกเหนือจากความสะดวกในการใช้งานและความชอบส่วนตัวสิ่งเหล่านี้จะทำได้ดีกว่าปกติดังนั้นจึงไม่มีเหตุผลที่จะแขวนมัน ส่วนใหญ่สามารถขยายได้ตามความต้องการของคุณ

ยังถ้าฉันต้องเลือกชุดวันนี้ฉันจะไปกับ LogBack + SLF4J แต่ถ้าคุณถามฉันไม่กี่ปีต่อมาฉันขอแนะนำ Log4J กับ Apache Commons Logging ดังนั้นโปรดคอยติดตามการพึ่งพาของคุณและพัฒนาไปกับพวกเขา


1
SLF4J และ LogBack ถูกเขียนโดยคนที่เขียน Log4J
Mike Partridge

4
สำหรับผู้ที่อาจกังวลเกี่ยวกับผลกระทบด้านประสิทธิภาพของการบันทึก: slf4j.org/faq.html#logging_performance
Mike Partridge

2
เป็นมูลค่าการกล่าวขวัญว่ามันไม่ชัดเจนว่าคุณควรทำบันทึกstaticเพราะมันช่วยประหยัดหน่วยความจำเล็กน้อย แต่ทำให้เกิดปัญหาในบางกรณี: slf4j.org/faq.html#declared_static
Reinstate Monica

1
@MikePartridge: ฉันทราบถึงเนื้อหาของลิงก์ แต่ก็ยังไม่ป้องกันการประเมินพารามิเตอร์เช่น เหตุผลที่การบันทึกพารามิเตอร์แบบ byt นั้นมีประสิทธิภาพมากกว่านั้นเนื่องจากการประมวลผลข้อความบันทึกจะไม่เกิดขึ้น (สตริงการต่อข้อมูลที่สะดุดตา) อย่างไรก็ตามการเรียกเมธอดใด ๆ จะถูกเรียกใช้งานหากถูกส่งเป็นพารามิเตอร์ ดังนั้นขึ้นอยู่กับกรณีการใช้งานของคุณบล็อกอาจมีประโยชน์ และตามที่กล่าวไว้ในโพสต์พวกเขายังสามารถเป็นประโยชน์สำหรับคุณที่จะจัดกลุ่มกิจกรรมที่เกี่ยวข้องกับการดีบักอื่น ๆ (ไม่ใช่แค่การบันทึก) ที่จะเกิดขึ้นเมื่อเปิดใช้งานระดับ DEBUG
เฮย์เล็ม

1
@haylem - จริงฉันผิด
Mike Partridge

2

ใช้กรอบการบันทึก

ส่วนใหญ่มีวิธีการแบบคงที่จากโรงงาน

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

จากนั้นคุณสามารถส่งออกรหัสบันทึกของคุณด้วยระดับที่แตกต่างกัน:

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

จากนั้นจะมีไฟล์เดียวที่คุณสามารถกำหนดว่าข้อความใดสำหรับแต่ละคลาสที่ควรเขียนไปยังเอาต์พุตของบันทึก (ไฟล์หรือ stderr)


1

นี่คือสิ่งที่เฟรมเวิร์กการบันทึกเช่น log4j หรือ slf4j ที่ใหม่กว่ามีไว้สำหรับ ช่วยให้คุณสามารถควบคุมการบันทึกในรายละเอียดที่ยอดเยี่ยมและกำหนดค่าแม้ในขณะที่แอปพลิเคชันทำงานอยู่


0

เฟรมเวิร์กการบันทึกเป็นวิธีที่แน่นอน อย่างไรก็ตามคุณต้องมีชุดทดสอบที่ดี การครอบคลุมการทดสอบที่ดีมักจะไม่จำเป็นต้องมีการดีบักเอาต์พุตพร้อมกัน


หากคุณใช้เฟรมเวิร์กการบันทึกและมีการบันทึกการดีบักที่พร้อมใช้งาน - จะมีเวลาที่สิ่งนี้จะช่วยให้คุณประหยัดจากวันที่เลวร้ายจริงๆ
Fortyrunner

1
ฉันไม่ได้บอกว่าคุณไม่ควรเข้าสู่ระบบ ฉันบอกว่าคุณต้องมีการทดสอบก่อน
Dima
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.