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の場合、フレームワークがisDebugEnabled()メソッドを複数回(呼び出しごとに)呼び出す必要がない、単一のメソッドまたはクラスで複数のデバッグステートメントを使用している場合に役立つことがあります。この場合、isDebugEnabled()メソッドを1回だけ呼び出します。Log4Jがデバッグレベルに設定されている場合、実際にはisDebugEnabled()メソッドを2回呼び出します。

  1. debugEnabled変数に値を割り当てる場合、および
  2. 実際にはlogger.debug()メソッドによって呼び出されます。

私たちは、複数書く場合とは思わないlogger.debug()メソッドやクラスで声明をして呼び出すdebug()オプション1に記載の方法をので、それはオプション2と比較してLog4JのフレームワークのためのオーバーヘッドであるisDebugEnabled()ことは、(コードの面で)非常に小さい方法であるかもしれませんインライン化の良い候補になる。

回答:


248

この特定のケースでは、オプション1の方が適しています。

ガードステートメント(チェックisDebugEnabled())はtoString()、さまざまなオブジェクトのメソッドの呼び出しと結果の連結を伴う場合に、ログメッセージの潜在的にコストのかかる計算を防ぐためにあります。

与えられた例では、ログメッセージは定数文字列であるため、ロガーにそれを破棄させることは、ロガーが有効になっているかどうかを確認するのと同じくらい効率的であり、ブランチが少ないためコードの複雑さを軽減します。

さらに良いのは、ログステートメントがフォーマット指定とロガーによって置換される引数のリストを受け取る、より最新のロギングフレームワークを使用することです。ただし、「遅延」では、ロガーが有効になっている場合のみです。これはslf4jが採用したアプローチです

詳細と、log4jでこのようなことを行う例については、関連する質問に対する私の回答を参照しください。


3
log5jは、slf4jとほぼ同じようにlog4jを拡張します
ビルミシェル

これは、java.util.Loggingのアプローチでもあります。
ポール

@Geekログレベルが高く設定されているため、ログイベントが無効になっている場合はより効率的です。ここ
エリクソン2013

1
これはlog4j 2で変更されましたか?
SnakeDoc

3
@SnakeDocいいえ。メソッド呼び出しの基本です。メソッドarg-list内の式は、呼び出しの前に効果的に評価されます。これらの式がa)高価であると見なされ、b)特定の条件下でのみ必要な場合(デバッグが有効になっている場合など)は、呼び出しの周りに条件チェックを置くことが唯一の選択肢であり、フレームワークはそれを行うことができません。フォーマッタベースのログメソッドの重要な点は、いくつかのオブジェクト(基本的には無料)を渡すことができtoString()、必要な場合にのみロガーが呼び出されることです。
SusanW 2017

31

オプション1ではメッセージ文字列が定数であるため、ログステートメントを条件でラップしてもまったくメリットはありません。逆に、ログステートメントがデバッグ可能になっている場合は、isDebugEnabled()メソッドで1回、メソッドで1 回、2回評価します。debug()方法。呼び出しのコストはisDebugEnabled()5〜30ナノ秒のオーダーであり、ほとんどの実用的な目的では無視できます。したがって、オプション2はコードを汚染し、他の利益をもたらさないため、望ましくありません。


17

の使用は、isDebugEnabled()文字列を連結してログメッセージを作成する場合のために予約されています。

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

ただし、この例では、文字列をログに記録するだけで、連結などの操作を実行しないため、速度は向上しません。したがって、コードに膨張を追加し、読みにくくしているだけです。

私は個人的に、次のようにStringクラスでJava 1.5形式の呼び出しを使用しています。

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より40倍遅く、slf4jには2つのパラメーターの制限があります。ここの数字を参照してください: stackoverflow.com/questions/925423/… プロダクションシステムがデバッグステートメントでフォーマット操作が無駄になっているプロファイラーグラフがたくさんあります。 INFOまたはERRORのログレベルで実行
AztecWarrior_25


8

ショートバージョン:ブールのisDebugEnabled()チェックを実行することもできます。

理由:
1-複雑なロジック/文字列連結の場合。がデバッグ文に追加され、すでにチェックが行われています。
2-「複雑な」デバッグステートメントにステートメントを選択的に含める必要はありません。すべてのステートメントはそのように含まれています。
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 2017

7

他の人がガードステートメントを使用することについて述べたように、文字列の作成が時間のかかる呼び出しである場合にのみ、本当に役立ちます。これの具体例は、文字列を作成すると遅延読み込みがトリガーされる場合です。

この問題は、Simple Logging Facade for 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に設定されている場合でも、文字列の連結は行われます。アプリケーションに文字列連結を含む多くのデバッグレベルのメッセージが含まれている場合、特にjdk 1.4以下では、確かにパフォーマンスが低下します。(jdk internallの新しいバージョンがstringbuffer.append()を実行するかどうかはわかりません)。

そのため、オプション2は安全です。文字列の連結でさえ起こりません。


3

@ericksonのように、状況によって異なります。私が思い出すと、isDebugEnabledすでにdebug()Log4j のメソッドに組み込まれています。
オブジェクトのループ、計算の実行、文字列の連結など、デバッグステートメントで高価な計算を行わない限り、私の意見では問題ありません。

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

以下のために、単一の行、私は連結をしないこの方法では、ログメッセージの三内部を使用します。

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は、Supplierをサポートしています(現在は独自の実装ですが、ドキュメントによると、バージョン3.0ではJavaのSupplierインターフェースを使用する予定です)。これについてはマニュアルでもう少し読むことができます。これにより、ログに記録される予定の場合にのみメッセージを作成するサプライヤに、高価なログメッセージの作成を組み込むことができます。

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

2

デバッグテキストで文字列を連結することは一般的であるため、速度が向上します。

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

1
jdk 1.5以降を使用している場合は、文字列を連結しても違いはないと思います。
サイレントウォリアー

どうして?JDK5は何が違うのでしょうか?
javashlook 2009年

1
jdk 1.5では、単一のステートメントで文字列を連結した場合、内部ではStringBuffer.append()メソッドのみを使用します。したがって、パフォーマンスには影響しません。
サイレントウォリアー

2
文字列の連結には間違いなく時間がかかります。しかし、私がそれを「高価」と表現するかどうかはわかりません。上記の例ではどのくらいの時間が節約されますか?周囲のコードが実際に何をしているかと比較して?(たとえば、データベースの読み取りまたはメモリ内計算)。これらの種類のステートメントは修飾する必要があると思います
ブライアンアグニュー

1
JDK 1.4でも、単純な文字列連結で新しいStringオブジェクトを作成しません。文字列をまったく表示しない場合にStringBuffer.append()を使用すると、パフォーマンスが低下します。
javashlook 2009年

1

Log4jバージョン2.4(またはslf4j-api 2.0.0-alpha1)以降、Fluent 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を使用する場合、高速なブールチェックを実行しています。オプション1では、メソッド呼び出し(スタックにデータをプッシュ)を実行してから、ブール値チェックを実行します。私が見る問題は一貫性です。デバッグ文と情報文の一部がラップされ、一部がラップされていない場合、一貫したコードスタイルではありません。さらに、後で誰かがデバッグ文を変更して連結文字列を含めることができますが、これはまだかなり高速です。大きなアプリケーションでデバッグと情報ステートメントをラップしてプロファイルすると、パフォーマンスが数パーセント低下したことがわかりました。それほど多くはありませんが、作業に値するものにするのに十分です。IntelliJでいくつかのマクロを設定して、ラップされたデバッグおよび情報ステートメントを自動的に生成します。


0

オプション2は非常に高価ではないため、ほとんどの場合、事実上、使用することをお勧めします。

ケース1:log.debug( "1つの文字列")

Case2:log.debug( "1つの文字列" + "2つの文字列" + object.toString + object2.toString)

これらのいずれかが呼び出された時点で、log.debug内のパラメーター文字列(CASE 1またはCase2)が評価される必要があります。それが誰もが「高価」という意味です。その前に 'isDebugEnabled()'の条件がある場合、これらはパフォーマンスを保存する場所である評価する必要はありません。


0

2.x以降、Apache Log4jにはこのチェックが組み込まれているため、isDebugEnabled()もう必要ありません。a 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.