Trong log4j, việc kiểm tra isDebugEnables trước khi đăng nhập có cải thiện hiệu suất không?


207

Tôi đang sử dụng Log4J trong ứng dụng của mình để đăng nhập. Trước đây tôi đã sử dụng cuộc gọi gỡ lỗi như:

Lựa chọn 1:

logger.debug("some debug text");

nhưng một số liên kết gợi ý rằng tốt hơn hết là kiểm tra isDebugEnabled()trước, như:

Lựa chọn 2:

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

Vì vậy, câu hỏi của tôi là " Liệu tùy chọn 2 có cải thiện hiệu suất theo cách nào không? ".

Bởi vì trong mọi trường hợp, khung Log4J đều có cùng kiểm tra gỡ lỗi. Đối với tùy chọn 2, có thể có ích nếu chúng ta đang sử dụng nhiều câu lệnh gỡ lỗi trong một phương thức hoặc lớp duy nhất, trong đó khung không cần gọi isDebugEnabled()phương thức nhiều lần (trên mỗi cuộc gọi); trong trường hợp này, nó chỉ gọi isDebugEnabled()phương thức một lần và nếu Log4J được cấu hình ở mức gỡ lỗi thì thực tế nó gọi isDebugEnabled()phương thức hai lần:

  1. Trong trường hợp gán giá trị cho biến debugEnables và
  2. Thực tế được gọi bằng phương thức logger.debug ().

Tôi không nghĩ rằng nếu chúng ta viết nhiều logger.debug()câu lệnh trong phương thức hoặc lớp và debug()phương thức gọi theo tùy chọn 1 thì đó là chi phí cho khung Log4J so với tùy chọn 2. Vì đây isDebugEnabled()là một phương thức rất nhỏ (về mặt mã), nó có thể là ứng cử viên tốt cho nội tuyến.

Câu trả lời:


247

Trong trường hợp cụ thể này, Lựa chọn 1 là tốt hơn.

Tuyên bố bảo vệ (kiểm tra isDebugEnabled()) là để ngăn chặn tính toán có thể tốn kém của thông điệp tường trình khi nó liên quan đến việc gọi các toString()phương thức của các đối tượng khác nhau và nối các kết quả.

Trong ví dụ đã cho, thông điệp tường trình là một chuỗi không đổi, vì vậy, hãy để logger loại bỏ nó cũng hiệu quả như kiểm tra xem logger có được kích hoạt hay không và nó làm giảm độ phức tạp của mã vì có ít nhánh hơn.

Tốt hơn nữa là sử dụng một khung ghi nhật ký cập nhật hơn, trong đó các câu lệnh nhật ký có một đặc tả định dạng và một danh sách các đối số sẽ được thay thế bởi logger nhưng "lười biếng", chỉ khi trình ghi nhật ký được bật. Đây là cách tiếp cận được thực hiện bởi slf4j .

Xem câu trả lời của tôi cho một câu hỏi liên quan để biết thêm thông tin và một ví dụ về việc làm một cái gì đó như thế này với log4j.


3
log5j mở rộng log4j theo cách tương tự như slf4j
Bill Michell

Đây cũng là cách tiếp cận của java.util.Logging.
Paul

@Geek Sẽ hiệu quả hơn khi sự kiện nhật ký bị vô hiệu hóa vì mức độ nhật ký được đặt ở mức cao. Xem phần có tiêu đề "Sự cần thiết phải đăng nhập có điều kiện" trong câu trả lời
erickson

1
Điều này đã thay đổi trong log4j 2?
SnakeDoc

3
@SnakeDoc Không. Đó là cơ bản để gọi phương thức: các biểu thức trong danh sách phương thức được đánh giá hiệu quả trước cuộc gọi. Nếu các biểu thức đó là a) được coi là đắt tiền và b) chỉ muốn trong một số điều kiện nhất định (ví dụ: khi gỡ lỗi được bật), thì lựa chọn duy nhất của bạn là đặt kiểm tra điều kiện xung quanh cuộc gọi và khung không thể làm điều đó cho bạn. Điều với các phương thức nhật ký dựa trên định dạng là bạn có thể vượt qua một số Đối tượng (về cơ bản là miễn phí) và trình ghi nhật ký sẽ toString()chỉ gọi nếu được yêu cầu.
SusanW

31

Vì trong tùy chọn 1, chuỗi thông báo là một hằng số, hoàn toàn không có lợi ích nào trong việc gói câu lệnh ghi nhật ký với một điều kiện, ngược lại, nếu câu lệnh nhật ký được gỡ lỗi, bạn sẽ đánh giá hai lần, một lần trong isDebugEnabled()phương thức và một lần vào debug()phương pháp. Chi phí gọi isDebugEnabled()là theo thứ tự từ 5 đến 30 nano giây, không đáng kể cho hầu hết các mục đích thực tế. Do đó, tùy chọn 2 là không mong muốn vì nó gây ô nhiễm mã của bạn và không cung cấp lợi ích nào khác.


17

Việc sử dụng isDebugEnabled()được dành riêng khi bạn xây dựng thông điệp tường trình bằng cách nối Chuỗi:

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

Tuy nhiên, trong ví dụ của bạn không có tốc độ tăng vì bạn chỉ đang đăng nhập một Chuỗi và không thực hiện các hoạt động như nối. Do đó, bạn chỉ cần thêm bloat vào mã của mình và làm cho nó khó đọc hơn.

Cá nhân tôi sử dụng các cuộc gọi định dạng Java 1.5 trong lớp String như thế này:

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

Tôi nghi ngờ có nhiều tối ưu hóa nhưng nó dễ đọc hơn.

Mặc dù vậy, hãy lưu ý rằng hầu hết các API ghi nhật ký cung cấp định dạng như thế này: ví dụ: slf4j cung cấp các thông tin sau:

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

mà thậm chí còn dễ đọc hơn.


8
Việc bạn sử dụng String.format (...), trong khi làm cho dòng nhật ký dễ đọc hơn, trên thực tế có thể ảnh hưởng đến hiệu suất theo cách xấu. Cách SLF4J thực hiện việc này sẽ gửi các tham số vào phương thức logger.debug và ở đó việc đánh giá isDebugEnables được thực hiện trước khi chuỗi được xây dựng. Cách bạn đang thực hiện, với String.format (...), chuỗi sẽ được xây dựng trước khi gọi phương thức tới logger.debug, do đó bạn sẽ phải trả tiền phạt cho việc xây dựng chuỗi ngay cả khi mức độ gỡ lỗi là không được kích hoạt Xin lỗi vì đã chọn nit, chỉ cố gắng tránh nhầm lẫn cho người mới ;-)
StFS

2
String.format chậm hơn 40 lần so với concat & slf4j có giới hạn 2 params Xem các số ở đây: stackoverflow.com/questions/925423/. Tôi đã thấy nhiều biểu đồ profiler trong đó hoạt động định dạng bị lãng phí trong các câu lệnh gỡ lỗi khi hệ thống sản xuất bị lãng phí chạy ở cấp độ nhật ký của INFO hoặc ERROR
AztecWar Warrior_25


8

Phiên bản ngắn: Bạn cũng có thể thực hiện kiểm tra boolean isDebugEnables ().

Lý do:
1- Nếu phức tạp logic / chuỗi concat. được thêm vào câu lệnh gỡ lỗi của bạn, bạn sẽ có chỗ kiểm tra.
2- Bạn không phải chọn lọc bao gồm tuyên bố về các câu lệnh gỡ lỗi "phức tạp". Tất cả các tuyên bố được bao gồm theo cách đó.
3- Gọi log.debug thực hiện như sau trước khi đăng nhập:

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

Điều này về cơ bản giống như nhật ký cuộc gọi. hoặc mèo. isDebugEnables ().

TUY NHIÊN! Đây là những gì các nhà phát triển log4j nghĩ (vì nó nằm trong javadoc của họ và có lẽ bạn nên đi theo nó.)

Đây là phương pháp

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

Đây là javadoc cho nó

/**
*  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
Cảm ơn bạn đã bao gồm JavaDoc. Tôi biết rằng tôi đã thấy lời khuyên này ở đâu đó trước đây và đang cố gắng tìm một tài liệu tham khảo dứt khoát. Đây là, nếu không dứt khoát, ít nhất là thông tin rất tốt.
Simon Peter Chappell

7

Như những người khác đã đề cập bằng cách sử dụng câu lệnh bảo vệ chỉ thực sự hữu ích nếu việc tạo chuỗi là một cuộc gọi tốn thời gian. Ví dụ cụ thể về điều này là khi tạo chuỗi sẽ kích hoạt một số tải lười biếng.

Điều đáng chú ý là vấn đề này có thể được hoàn thành tránh bằng cách sử dụng Mặt tiền ghi nhật ký đơn giản cho Java hoặc (SLF4J) - http://www.slf4j.org/manual.html . Điều này cho phép các cuộc gọi phương thức như:

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

Điều này sẽ chỉ chuyển đổi các tham số được truyền thành chuỗi nếu gỡ lỗi được bật. SLF4J như tên gọi của nó chỉ là một mặt tiền và các cuộc gọi đăng nhập có thể được chuyển đến log4j.

Bạn cũng có thể rất dễ dàng "cuộn" phiên bản này của riêng bạn.

Hi vọng điêu nay co ich.


6

Lựa chọn 2 là tốt hơn.

Per se nó không cải thiện hiệu suất. Nhưng nó đảm bảo hiệu suất không suy giảm. Đây là cách.

Thông thường chúng tôi mong đợi logger.debug (someString);

Nhưng thông thường, khi ứng dụng phát triển, thay đổi nhiều tay, đặc biệt là các nhà phát triển, bạn có thể thấy

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

và như thế.

Ngay cả khi mức ghi nhật ký được đặt thành LRI hoặc FATAL, việc nối chuỗi vẫn xảy ra! Nếu ứng dụng chứa nhiều thông báo mức DEBUG với các chuỗi nối, thì chắc chắn nó sẽ đạt hiệu năng cao, đặc biệt là với jdk 1.4 trở xuống. (Tôi không chắc liệu các phiên bản sau của jdk internall có thực hiện bất kỳ chuỗi ký tự nào không.append ()).

Đó là lý do tại sao Lựa chọn 2 là an toàn. Ngay cả các chuỗi nối không xảy ra.


3

Giống như @erickson, nó phụ thuộc. Nếu tôi nhớ lại, isDebugEnabledđã được xây dựng theo debug()phương thức của Log4j.
Miễn là bạn không thực hiện một số tính toán đắt tiền trong các câu lệnh gỡ lỗi của mình, như vòng lặp trên các đối tượng, thực hiện tính toán và nối chuỗi, theo ý kiến ​​của tôi là ổn.

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

sẽ tốt hơn như

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

Đối với một dòng duy nhất , tôi sử dụng một ternary bên trong thông điệp đăng nhập, Theo cách này, tôi không thực hiện nối ghép:

ej:

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

Tôi làm:

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

Nhưng đối với nhiều dòng

ej.

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

Tôi làm:

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

3

Vì nhiều người có thể đang xem câu trả lời này khi tìm kiếm log4j2 và gần như tất cả các câu trả lời hiện tại không xem xét log4j2 hoặc những thay đổi gần đây trong nó, nên hy vọng điều này sẽ trả lời câu hỏi.

log4j2 hỗ trợ các Nhà cung cấp (hiện đang triển khai riêng của họ, nhưng theo tài liệu, nó được lên kế hoạch sử dụng giao diện Nhà cung cấp của Java trong phiên bản 3.0). Bạn có thể đọc thêm một chút về điều này trong hướng dẫn . Điều này cho phép bạn đặt việc tạo thông điệp nhật ký đắt tiền vào một nhà cung cấp chỉ tạo thông điệp nếu nó sẽ được ghi lại:

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

2

Nó cải thiện tốc độ vì nó phổ biến để nối các chuỗi trong văn bản gỡ lỗi rất tốn kém, ví dụ:

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

1
Nếu chúng ta đang sử dụng jdk 1.5 trở đi thì tôi nghĩ các chuỗi nối sẽ không tạo ra sự khác biệt nào.
Chiến binh thầm lặng

Làm thế nào mà? JDK5 sẽ làm gì khác nhau?
javashlook

1
Trong jdk 1.5 nếu chúng ta ghép các chuỗi trong một câu lệnh thì bên trong nó chỉ sử dụng phương thức StringBuffer.append (). Vì vậy, nó không ảnh hưởng đến hiệu suất.
Chiến binh thầm lặng

2
Chuỗi kết nối chắc chắn mất thời gian. Tuy nhiên tôi không chắc chắn tôi sẽ mô tả nó là 'đắt'. Bao nhiêu thời gian được tiết kiệm trong ví dụ trên? So với những gì các mã xung quanh đang thực sự làm gì? (ví dụ: cơ sở dữ liệu đọc hoặc tính toán trong bộ nhớ). Tôi nghĩ những loại báo cáo này cần phải đủ tiêu chuẩn
Brian Agnew

1
Ngay cả JDK 1.4 cũng sẽ không tạo các đối tượng String mới với cách nối chuỗi đơn giản. Hình phạt hiệu suất đến từ việc sử dụng StringBuffer.append () khi không có chuỗi nào được hiển thị.
javashlook

1

Vì phiên bản Log4j2.4 (hoặc slf4j-api 2.0.0-alpha1) tốt hơn nên sử dụng API thông thạo (hoặc hỗ trợ lambda Java 8 để ghi nhật ký lười biếng ), hỗ trợ Supplier<?>cho đối số thông điệp nhật ký, có thể được cung cấp bởi lambda :

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

HOẶC với API slf4j:

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

0

Nếu bạn sử dụng tùy chọn 2, bạn đang thực hiện kiểm tra Boolean nhanh. Trong tùy chọn một, bạn đang thực hiện một cuộc gọi phương thức (đẩy công cụ lên ngăn xếp) và sau đó thực hiện kiểm tra Boolean vẫn nhanh. Vấn đề tôi thấy là sự nhất quán. Nếu một số câu lệnh gỡ lỗi và thông tin của bạn được gói và một số không phải là kiểu mã nhất quán. Ngoài ra, ai đó sau này có thể thay đổi câu lệnh gỡ lỗi để bao gồm các chuỗi nối, vẫn còn khá nhanh. Tôi thấy rằng khi chúng tôi đưa ra tuyên bố gỡ lỗi và thông tin trong một ứng dụng lớn và định hình nó, chúng tôi đã tiết kiệm được một vài điểm phần trăm trong hiệu suất. Không nhiều, nhưng đủ để làm cho nó xứng đáng với công việc. Bây giờ tôi có một vài thiết lập macro trong IntelliJ để tự động tạo ra các câu lệnh gỡ lỗi và thông tin được gói cho tôi.


0

Tôi khuyên bạn nên sử dụng Tùy chọn 2 vì thực tế không phải là siêu đắt.

Trường hợp 1: log.debug ("một chuỗi")

Case2: log.debug ("một chuỗi" + "hai chuỗi" + object.toString + object2.toString)

Tại thời điểm một trong hai được gọi, chuỗi tham số trong log.debug (có thể là CASE 1 hoặc Case2) ĐÃ được đánh giá. Đó là những gì mọi người có nghĩa là 'đắt tiền'. Nếu bạn có một điều kiện trước nó, 'isDebugEnables ()', những điều này không phải được đánh giá đó là nơi lưu hiệu suất.


0

Kể từ 2.x, Apache Log4j đã tích hợp kiểm tra này, do đó isDebugEnabled()không còn cần thiết nữa. Chỉ cần làm một debug()và các tin nhắn sẽ bị chặn nếu không được kích hoạt.


-1

Log4j2 cho phép bạn định dạng các tham số thành một mẫu thông báo, tương tự như String.format()vậy, do đó không cần phải làm gì isDebugEnabled().

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

Mẫu log4j2.properies đơn giản:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.