ใน log4j การตรวจสอบ isDebugEnabled ก่อนการบันทึกปรับปรุงประสิทธิภาพหรือไม่


207

ฉันใช้Log4Jในแอปพลิเคชันของฉันเพื่อบันทึก ก่อนหน้านี้ฉันใช้การโทรดีบั๊กเช่น:

ตัวเลือกที่ 1:

logger.debug("some debug text");

แต่บางลิงค์แนะนำว่าควรตรวจสอบisDebugEnabled()ก่อนดีกว่าเช่น:

ตัวเลือก 2:

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

ดังนั้นคำถามของฉันคือ " ตัวเลือก 2 ปรับปรุงประสิทธิภาพหรือไม่ "

เพราะในกรณีใด ๆ กรอบ Log4J มีการตรวจสอบเดียวกันสำหรับ debugEnabled สำหรับตัวเลือกที่ 2 อาจเป็นประโยชน์ถ้าเราใช้คำสั่ง debug หลายคำสั่งในวิธีการเดียวหรือคลาสที่กรอบไม่จำเป็นต้องเรียกisDebugEnabled()วิธีการหลายครั้ง (ในการโทรแต่ละครั้ง); ในกรณีนี้จะเรียกisDebugEnabled()ใช้เมธอดเพียงครั้งเดียวและหาก Log4J ได้รับการกำหนดค่าให้ดีบั๊กระดับจริง ๆ แล้วมันจะเรียกisDebugEnabled()เมธอดสองครั้ง:

  1. ในกรณีที่กำหนดค่าให้กับตัวแปร debugEnabled และ
  2. เรียกจริงโดยวิธี logger.debug ()

ฉันไม่คิดว่าถ้าเราเขียนหลายlogger.debug()คำสั่งในวิธีการหรือชั้นเรียนและdebug()วิธีการโทรตามตัวเลือก 1 แล้วมันเป็นค่าใช้จ่ายสำหรับกรอบ Log4J เมื่อเทียบกับตัวเลือก 2 เนื่องจากisDebugEnabled()เป็นวิธีที่เล็กมาก (ในแง่ของรหัส) มันอาจ เป็นผู้สมัครที่ดีสำหรับการทำอินไลน์

คำตอบ:


247

ในกรณีนี้ตัวเลือกที่ 1 จะดีกว่า

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

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

ยังดีกว่าคือการใช้เฟรมเวิร์กการบันทึกที่เป็นปัจจุบันมากขึ้นโดยที่คำสั่งบันทึกใช้ข้อมูลจำเพาะรูปแบบและรายการของอาร์กิวเมนต์ที่จะถูกแทนที่ด้วยตัวบันทึก - แต่ "ขี้เกียจ" เฉพาะเมื่อตัวบันทึกถูกเปิดใช้งาน นี่คือวิธีการที่ถ่ายโดยslf4j

ดูคำตอบของฉันสำหรับคำถามที่เกี่ยวข้องสำหรับข้อมูลเพิ่มเติมและตัวอย่างของการทำสิ่งนี้กับ log4j


3
log5j ขยาย log4j ในทางเดียวกันมากเป็น slf4j
บิลมิเชลล์

นี่เป็นวิธีการของ java.util.Logging
พอล

@Geek จะมีประสิทธิภาพมากกว่าเมื่อปิดการใช้งานบันทึกเหตุการณ์เนื่องจากระดับการบันทึกถูกตั้งค่าไว้สูง ดูหัวข้อ "ความจำเป็นในการบันทึกข้อมูลตามเงื่อนไข" ในคำตอบ
erickson

1
สิ่งนี้มีการเปลี่ยนแปลงใน log4j 2 หรือไม่?
SnakeDoc

3
@SnakeDoc ไม่เป็นพื้นฐานในการเรียกใช้เมธอด: นิพจน์ในเมธอด arg-list จะถูกประเมินอย่างมีประสิทธิภาพก่อนการเรียก หากการแสดงออกเหล่านั้นเป็น) ถือว่ามีราคาแพงและ b) ต้องการเพียงภายใต้เงื่อนไขบางประการ (เช่นเมื่อเปิดใช้งานการแก้ไขข้อบกพร่อง) ตัวเลือกเดียวของคุณคือการตรวจสอบเงื่อนไขรอบการโทรและกรอบไม่สามารถทำได้สำหรับคุณ สิ่งที่มีวิธีการบันทึกที่ใช้ตัวจัดรูปแบบคือคุณสามารถผ่านวัตถุบางอย่าง (ซึ่งไม่มีค่าใช้จ่าย) และตัวบันทึกจะเรียกtoString()ใช้เมื่อจำเป็นเท่านั้น
SusanW

31

เนื่องจากในตัวเลือกที่ 1 สตริงข้อความมีค่าคงที่จึงไม่มีประโยชน์ในการห่อคำสั่งการบันทึกด้วยเงื่อนไขในทางตรงกันข้ามหากคำสั่งบันทึกการใช้งานถูกดีบักคุณจะได้รับการประเมินสองครั้งหนึ่งครั้งในisDebugEnabled()วิธีการและอีกครั้งในdebug()วิธี. ค่าใช้จ่ายในการเรียกใช้isDebugEnabled()อยู่ในระดับ 5 ถึง 30 นาโนวินาทีซึ่งควรจะเล็กน้อยสำหรับการใช้งานจริง ดังนั้นตัวเลือกที่ 2 ไม่เป็นที่ต้องการเพราะมันทำให้รหัสของคุณสกปรกและไม่ได้รับผลประโยชน์อื่น ๆ


17

การใช้งานisDebugEnabled()ถูกสงวนไว้เมื่อคุณกำลังสร้างข้อความบันทึกโดยการต่อสตริง:

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

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

ฉันเองใช้การเรียกรูปแบบ Java 1.5 ในคลาส String เช่นนี้:

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

ฉันสงสัยว่ามีการเพิ่มประสิทธิภาพมาก แต่อ่านง่ายขึ้น

โปรดทราบว่า API การบันทึกส่วนใหญ่มีการจัดรูปแบบเช่นนี้อยู่นอกกล่อง: ตัวอย่าง slf4j จัดเตรียมสิ่งต่อไปนี้:

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

ซึ่งง่ายต่อการอ่าน


8
การใช้งาน String.format (... ) ของคุณในขณะที่ทำให้การอ่านง่ายขึ้นอาจส่งผลกระทบต่อประสิทธิภาพการทำงานในทางที่ไม่ดี วิธี SLF4J ในการทำเช่นนี้ส่งพารามิเตอร์ไปยังวิธีการ logger.debug และมีการประเมินผลของ isDebugEnabled เสร็จก่อนที่จะสร้างสตริง วิธีที่คุณทำกับ String.format (... ) สตริงจะถูกสร้างขึ้นก่อนที่การเรียกเมธอดไปที่ logger.debug จะเสร็จสิ้นดังนั้นคุณจะต้องจ่ายค่าปรับของการสร้างสตริงแม้ว่าระดับการดีบักจะเป็นอย่างไร ไม่เปิดใช้งาน ขออภัยสำหรับการหยิบจู้จี้เพียงพยายามหลีกเลี่ยงความสับสนสำหรับมือใหม่ ;-)
StFS

2
String.format ช้ากว่า concat & slf4j 40 เท่ามีข้อ จำกัด ของ 2 params ดูตัวเลขที่นี่: stackoverflow.com/questions/925423/ … ฉันได้เห็นกราฟ profiler จำนวนมากซึ่งการดำเนินการรูปแบบสูญเปล่าในการแก้ปัญหางบเมื่อระบบการผลิตเป็น ทำงานที่ระดับล็อกของ INFO หรือข้อผิดพลาด
AztecWarrior_25

10

ใน Java 8 คุณไม่ต้องใช้isDebugEnabled()เพื่อปรับปรุงประสิทธิภาพ

https://logging.apache.org/log4j/2.0/manual/api.html#Java_8_lambda_support_for_lazy_logging

import java.util.logging.Logger;
...
Logger.getLogger("hello").info(() -> "Hello " + name);

เด็ดมาก แต่ไม่รองรับใน slft4j จนกระทั่งรุ่น 2 (ยังอยู่ในอัลฟ่า) :(
Amalgovinus

8

เวอร์ชั่นย่อ: คุณอาจทำการตรวจสอบบูลีน isDebugEnabled ()

เหตุผล:
1- ถ้าซับซ้อนตรรกะ / สตริง concat ถูกเพิ่มไปยังรายการแก้ไขข้อบกพร่องของคุณคุณจะมีการเช็คอินอยู่แล้ว
2- คุณไม่จำเป็นต้องเลือกคำสั่งนั้นในคำสั่ง debug "complex" งบทั้งหมดรวมอยู่ในวิธีนั้น
3- การเรียก log.debug ดำเนินการต่อไปนี้ก่อนที่จะเข้าสู่ระบบ:

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

นี่เป็นพื้นเหมือนกับบันทึกการโทร หรือแมว isDebugEnabled ()

อย่างไรก็ตาม! นี่คือสิ่งที่นักพัฒนา log4j คิด (เช่นเดียวกับที่อยู่ใน javadoc และคุณน่าจะไปด้วย)

นี่คือวิธีการ

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

นี่คือ javadoc สำหรับมัน

/**
*  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 ฉันรู้ว่าฉันเคยเห็นคำแนะนำนี้มาก่อนและพยายามค้นหาข้อมูลอ้างอิงที่ชัดเจน นี่คือถ้าไม่ชัดเจนอย่างน้อยก็ทราบดี
Simon Peter Chappell

7

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

มันเป็นที่น่าสังเกตว่าปัญหานี้สามารถจะแล้วเสร็จโดยใช้หลีกเลี่ยงการเข้าสู่ระบบแบบธรรมดาซุ้มสำหรับ Java หรือ (SLF4J) - http://www.slf4j.org/manual.html วิธีนี้ทำให้สามารถเรียกใช้เมธอดเช่น:

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

สิ่งนี้จะแปลงพารามิเตอร์ที่ส่งผ่านเป็นสตริงหากเปิดใช้งานการดีบัก SLF4J เนื่องจากชื่อแนะนำเป็นเพียงส่วนหน้าและการเรียกการบันทึกสามารถส่งผ่านไปยัง log4j

นอกจากนี้คุณยังสามารถ "หมุนรุ่นของคุณเอง" ได้อย่างง่ายดายมาก

หวังว่านี่จะช่วยได้


6

ตัวเลือก 2 ดีกว่า

โดยรวมแล้วมันไม่ได้ปรับปรุงประสิทธิภาพ แต่จะทำให้ประสิทธิภาพไม่ลดลง นี่คือวิธี

โดยปกติเราคาดว่า logger.debug (someString);

แต่โดยปกติเมื่อแอปพลิเคชันเติบโตขึ้นการเปลี่ยนแปลงหลาย ๆ มือนักพัฒนามือใหม่คุณสามารถเห็นได้

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

และไม่ชอบ

แม้ว่าระดับการบันทึกจะถูกตั้งค่าเป็น ERROR หรือ FATAL การต่อสตริงจะเกิดขึ้น! หากแอปพลิเคชั่นประกอบด้วยข้อความระดับ DEBUG จำนวนมากที่มีการต่อสตริงแล้วจะต้องใช้ประสิทธิภาพการทำงานโดยเฉพาะอย่างยิ่งกับ jdk 1.4 หรือต่ำกว่า (ฉันไม่แน่ใจว่ารุ่นที่ใหม่กว่าของ jdk internall ทำ stringbuffer.append () ใด ๆ

นั่นเป็นเหตุผลที่ตัวเลือกที่ 2 มีความปลอดภัย แม้แต่การต่อสตริงก็ยังไม่เกิดขึ้น


3

เช่นเดียวกับ @erickson มันขึ้นอยู่กับ ถ้าฉันจำisDebugEnabledได้ว่าสร้างในdebug()วิธีของ Log4j
ตราบใดที่คุณไม่ได้คำนวณราคาแพงในงบ debug ของคุณเช่นวนรอบวัตถุดำเนินการคำนวณและสตริงที่ต่อกันคุณก็ไม่เป็นไร

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

จะดีกว่า

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

สำหรับบรรทัดเดียวฉันใช้ ternary ภายในข้อความการบันทึกด้วยวิธีนี้ฉันไม่ทำการต่อข้อมูล:

EJ:

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

ฉันทำ:

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

แต่สำหรับโค้ดหลายบรรทัด

EJ

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

ฉันทำ:

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

3

เนื่องจากหลายคนอาจดูคำตอบนี้เมื่อค้นหา log4j2 และเกือบทุกคำตอบปัจจุบันไม่พิจารณา log4j2 หรือการเปลี่ยนแปลงล่าสุดคำตอบนี้หวังว่าควรตอบคำถามนี้

log4j2 รองรับผู้ให้บริการ s (ปัจจุบันมีการนำไปใช้งานเอง แต่ตามเอกสารประกอบมีการวางแผนที่จะใช้ส่วนต่อประสานซัพพลายเออร์ของ Java ในเวอร์ชัน 3.0) คุณสามารถอ่านนิด ๆ หน่อย ๆ เกี่ยวกับเรื่องนี้ในคู่มือ สิ่งนี้ช่วยให้คุณสามารถสร้างข้อความบันทึกราคาแพงลงในซัพพลายเออร์ซึ่งจะสร้างข้อความเฉพาะเมื่อมันจะถูกบันทึกไว้:

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

2

มันช่วยเพิ่มความเร็วเพราะมันเป็นเรื่องปกติที่จะเชื่อมสตริงในข้อความดีบั๊กซึ่งมีราคาแพงเช่น:

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

1
หากเราใช้ jdk 1.5 ขึ้นไปฉันคิดว่าสตริงที่ต่อกันจะไม่สร้างความแตกต่าง
Silent Warrior

มาทำไม JDK5 จะทำอะไรแตกต่างกันบ้าง
javashlook

1
ใน jdk 1.5 ถ้าเราเชื่อมสตริงในคำสั่งเดียวจากนั้นภายในจะใช้เมธอด StringBuffer.append () เท่านั้น ดังนั้นจึงไม่มีผลต่อประสิทธิภาพ
Silent Warrior

2
การต่อสายอักขระต้องใช้เวลาอย่างไม่ต้องสงสัย อย่างไรก็ตามฉันไม่แน่ใจว่าฉันจะอธิบายว่า 'แพง' ประหยัดเวลาในตัวอย่างด้านบนเท่าไหร่? เปรียบเทียบกับรหัสที่อยู่รอบ ๆ กำลังทำอะไรอยู่ (เช่นการอ่านฐานข้อมูลหรือการคำนวณในหน่วยความจำ) ฉันคิดว่าข้อความเหล่านี้จำเป็นต้องผ่านการรับรอง
Brian Agnew

1
แม้กระทั่ง JDK 1.4 จะไม่สร้างวัตถุ String ใหม่ด้วยการต่อสตริงอย่างง่าย การปรับประสิทธิภาพนั้นมาจากการใช้ StringBuffer.append () เมื่อไม่ควรแสดงสตริงเลย
javashlook

1

ตั้งแต่Log4jเวอร์ชั่น2.4(หรือslf4j-api 2.0.0-alpha1) จะดีกว่าการใช้API อย่างคล่องแคล่ว (หรือJava 8 แลมบ์ดารองรับการบันทึกสันหลังยาว ) ซึ่งรองรับSupplier<?>อาร์กิวเมนต์ข้อความบันทึกที่แลมบ์ดาสามารถให้ได้:

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

หรือด้วย slf4j API:

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

0

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


0

ฉันขอแนะนำให้ใช้ตัวเลือก 2 เป็นพฤตินัยสำหรับส่วนใหญ่เพราะมันไม่แพงมาก

กรณีที่ 1: log.debug ("หนึ่งสตริง")

กรณีที่ 2: log.debug ("หนึ่งสตริง" + "สองสตริง" + object.toString + object2.toString)

ณ เวลาใดเวลาหนึ่งสิ่งเหล่านี้จะถูกเรียกใช้สตริงพารามิเตอร์ภายใน log.debug (ไม่ว่าจะเป็นกรณีที่ 1 หรือ Case2) HAS ที่จะได้รับการประเมิน นั่นคือสิ่งที่ทุกคนหมายถึง 'แพง' หากคุณมีเงื่อนไขก่อนหน้า 'isDebugEnabled ()' สิ่งเหล่านี้ไม่จำเป็นต้องได้รับการประเมินซึ่งเป็นที่ที่ประสิทธิภาพถูกบันทึกไว้


0

ตั้งแต่ 2.x, Apache Log4j มีการตรวจสอบนี้ในตัวดังนั้นจึงisDebugEnabled()ไม่จำเป็นอีกต่อไป เพียงทำdebug()และข้อความจะถูกระงับหากไม่ได้เปิดใช้งาน


-1

Log4j2 ช่วยให้คุณสามารถจัดรูปแบบพารามิเตอร์ลงในแม่แบบข้อความคล้ายกับจึงทำไปด้วยความจำเป็นที่จะต้องทำString.format()isDebugEnabled()

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

ตัวอย่างง่ายๆ log4j2.properties:

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