47

SFL4J ロギングの推奨パターンは次のとおりであることを指摘する、大量の投稿や文書 (このサイトおよび他の場所) を読みました。

public class MyClass {
    final static Logger logger = LoggerFactory.getLogger(MyClass.class);

    public void myMethod() {
        //do some stuff
        logger.debug("blah blah blah");
    }
}

私の上司は、ラッパーを使用してログ呼び出しをインターセプトし、すべてのクラスでロガーを宣言する定型コードを避けることを好みます。

public class MyLoggerWrapper {
    public static void debug(Class clazz, String msg){
        LoggerFactory.getLogger(clazz).debug(msg));
    }
}

単純に次のように使用します。

public class MyClass {

    public void myMethod() {
        //do some stuff
        MyLoggerWrapper.debug(this.getClass(), "blah blah blah");
    }
}

ログを記録するたびにロガーをインスタンス化するのは多少コストがかかると思いますが、その仮定を裏付けるドキュメントを見つけることができませんでした。それに加えて、彼は確かにフレームワーク (LogBack または Log4J をまだ決定中です) がロガーを「キャッシュ」し、いずれにせよサーバーが容量をはるかに下回る状態で実行されているため、問題ではないと述べています。

このアプローチの潜在的な問題を指摘する助けはありますか?

4

11 に答える 11

31

このアプローチには明らかな問題が 1 つあります。文字列メッセージは を呼び出すたびに作成されdebug()ます。ラッパーでガード句を使用する明らかな方法はありません。

log4j/commons-logging/slf4j の標準的なイディオムは、次のようなガード句を使用することです。

if (log.isDebugEnabled()) log.debug("blah blah blah");

DEBUGレベルがロガーに対して有効になっていない場合、コンパイラーは、送信する可能性のある長い文字列を連結することを回避できるようにすることを目的としています。

if (log.isDebugEnabled()) log.debug("the result of method foo is " + bar 
     + ", and the length is " + blah.length());

「ロギングの (ない) 最速の方法は何ですか?」を参照してください。SLF4Jまたはlog4j FAQ で。

あなたの上司が提案する「ラッパー」に反対することをお勧めします。slf4j や commons-logging などのライブラリは、実際に使用されている基本的なロギング実装のファサードです。さらに、ロガーの各呼び出しは非常に長くなります - 上記を

 MyLoggerWrapper.debug(Foo.class, "some message");

これは、間接的なレイヤーを追加してコードを醜いものにする以外に本当の目的を果たさない、些細で重要でない「ラッピング」と難読化のタイプです。あなたの上司は、執着すべきもっと重要な問題を見つけることができると思います。

于 2010-10-13T13:26:33.383 に答える
12

を繰り返し呼び出してLoggerFactory.getLogger(clazz)も、毎回新しい Logger オブジェクトになるべきではありません。しかし、それは通話が無料であることを意味するものではありません。実際の動作はファサードの背後にあるロギング システムに依存しますが、既存のインスタンスを探すために、 各 getLogger が同時または同期データ構造1のルックアップを必要とする可能性が高くなります。

アプリケーションがMyLoggerWrapper.debugメソッドに対して多くの呼び出しを行う場合、これらすべてが合計されてパフォーマンスが大幅に低下する可能性があります。また、マルチスレッド アプリケーションでは、同時実行性のボトルネックになる可能性があります。

他の回答で言及されている他の問題も重要です。

  • logger.isDebugEnabled()デバッグが無効になっている場合、アプリケーションはオーバーヘッドを最小限に抑えるために使用できなくなります。
  • このMyLoggerWrapperクラスは、アプリケーションのデバッグ呼び出しのクラス名と行番号を隠します。
  • MyLoggerWrapper複数のロガー呼び出しを行う場合、使用するコードはおそらくより冗長になります。そして、冗長性は、読みやすさに最も影響を与える領域になります。つまり、ロギングが必要なことを行うメソッドで。

最後に、これは単に「それが行われる方法ではない」ということです。


1 - どうやらこれはHashtableLogback と Log4j にあり、同時実行のボトルネックの可能性が確実に存在することを意味します。これは、これらのロギング フレームワークに対する批判ではないことに注意してください。むしろ、このgetLogger方法は、この方法で使用するように設計/最適化されていません。

于 2010-10-13T13:51:21.223 に答える
12

ロガー オブジェクトは確実に再利用されるため、いずれにしても余分なインスタンス化は発生しません。私が見るより大きな問題は、ファイル/行番号情報が役に立たないことです。ロガーは、各メッセージが class 、行 12 から発行されたことを常に忠実に記録するためですLoggerWrapper:-(

OTOH SLF4Jはすでに、使用されている特定のロギング フレームワークを非表示にするラッパー ファサードであり、異なるロギング実装間で自由に変更できます。したがって、それをさらに別のラッパーの後ろに隠す意味はまったくありません。

于 2010-10-13T13:17:36.830 に答える
10

すでに述べた理由に加えて、上司の提案は悪いものです。理由は次のとおりです。

  • 何かをログに記録するたびに、ログとは関係のないものを繰り返し入力する必要があります。this.getClass()
  • 静的コンテキストと非静的コンテキストの間に不均一なインターフェイスを作成します(静的コンテキストには存在thisしないため)
  • 追加の不要なパラメーターはエラーの余地を生み出し、同じクラスのステートメントが異なるロガーに移動できるようにします(不注意なコピー貼り付けを考えてください)
  • ロガー宣言の74文字を節約する一方で、各ロギング呼び出しに27文字を追加します。これは、クラスがロガーを2回以上使用する場合、文字数の観点からボイラープレートコードを増やすことを意味します。
于 2010-10-13T14:48:58.263 に答える
7

次のようなものを使用する場合: MyLoggerWrapper.debug(this.getClass(), "blah") AOP フレームワークまたはコード インジェクション ツールを使用すると、間違ったクラス名が取得されます。クラス名はオリジンとは異なり、生成されたクラス名です。ラッパーを使用するもう 1 つの欠点: ログ ステートメントごとに、追加のコードを含める必要があります"MyClass.class"

ロガーの「キャッシング」は、使用するフレームワークによって異なります。ただし、その場合でも、作成するすべてのログ ステートメントに対して目的のロガーを検索する必要があります。したがって、メソッドに 3 つのステートメントがある場合、3 回ルックアップする必要があります。それをstatic変数として使用すると、一度だけ検索する必要があります。

if( log.isXXXEnabled() ){}そして前に言った:一連のステートメントを使用する能力を失う.

あなたの上司は、「コミュニティのデフォルトが受け入れられ、推奨される方法」に対して何を反対していますか? ラッパーを導入しても、効率が向上するわけではありません。代わりに、すべてのログ ステートメントにクラス名を使用する必要があります。しばらくすると、それを「改善」したいので、別の変数または別のラッパーを追加して、自分にとってより困難にします。

于 2010-10-13T14:57:22.253 に答える
4

推奨されるパターンが最も読みやすく、実装しやすいと言わざるを得ません。そこから逸脱する理由が見当たりません。特にメリットなし。

ただし、私の主なポイントは、前述のガードについてです。ログを明示的に保護することはお勧めしません。これはすでに log4j によって内部的に行われており、作業が重複しているためです。

log4j のソースをダウンロードし、Logger クラスと Category クラスを見て、自分の目で確かめてください。

于 2010-10-13T14:29:11.797 に答える
4

ここで SLF4J チームが述べたように、JDK 1.7 で導入された MethodLookup() を使用できます。

final static Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

このようにして、キーワード「this」を使用せずにクラスを参照できます。

于 2017-05-02T23:48:47.980 に答える
3

いいえ。コールスタックを台無しにする以外はありません。これにより、ログを実行しているコードのメソッド名とクラスを確認できるメソッドが中断されます。

slf4j の上に構築された独自の抽象化を含む Jetty Web コンテナーを検討することを検討してください。非常に素晴らしい。

于 2010-10-13T13:17:17.017 に答える
1

上司のアプローチが彼の考えを達成しない理由は 2 つあります。

小さい理由は、静的ロガーを追加するオーバーヘッドが無視できることです。結局のところ、ロガーのセットアップは、この非常に長いシーケンスの一部です。

  • クラスを見つけます。つまり、すべての .jar とディレクトリを調べます。Java コード。ファイルシステム呼び出しによるかなり大きなオーバーヘッド。などでヘルパー オブジェクトを作成できますFile
  • バイトコードをロードします。つまり、JVM 内のデータ構造にコピーします。ネイティブ コード。
  • バイトコードを検証します。ネイティブ コード。
  • バイトコードをリンクします。つまり、バイトコード内のすべてのクラス名を繰り返し処理し、それらを参照先のクラスへのポインターに置き換えます。ネイティブ コード。
  • 静的初期化子を実行します。ネイティブ コードからトリガーされる初期化子は、もちろん Java コードです。ロガーはここで作成されます。
  • しばらくしてから、おそらくクラスを JIT コンパイルします。ネイティブ コード。巨大なオーバーヘッド (とにかく他の操作と比較して)。

また、上司は何も保存しません。
への最初の呼び出しLoggerFactor.getLoggerは、ロガーを作成し、グローバルな名前からロガーへの HashMap に配置します。isXxxEnabledこれらを行うには、最初に Logger オブジェクトを構築する必要があるため、これは呼び出しでも発生します...
Class オブジェクトは、静的変数の追加のポインターを保持します。これは、パラメーターを渡すオーバーヘッド (clazz追加の命令とバイトコード内の追加のポインター サイズの参照) によって相殺されるため、少なくとも 1 バイトのクラス サイズが失われます。

このコードは、余分な間接化、LoggerFactory.getLogger(Class)uses Class#getName、および delegatesも行っていLoggerFactory.getLogger(String)ます。

上司がパフォーマンスではなく、静的宣言を単純にコピーする機能を求めている場合、呼び出しスタックを検査してクラス名を取得する関数を使用できます。関数には注釈@CallerSensitiveを付ける必要があり、新しい JVM を使用するたびにテストする必要があります。ユーザーがコードを実行している JVM を制御しないと、うまくいきません。

最も問題の少ないアプローチは、ロガーのインスタンス化をチェックする IDE を持つことです。これはおそらく、プラグインを見つけるか書くことを意味します。

于 2017-11-13T11:24:13.297 に答える