web-dev-qa-db-ja.com

LoggerFactory.getLogger(...)を毎回呼び出すことが推奨されないのはなぜですか?

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がまだ決定している)がロガーを「キャッシュ」し、サーバーがキャパシティを大幅に下回っているので問題にならないことを確認します。

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

42
danirod

このアプローチには1つの明らかな問題があります。Stringメッセージは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");

これは些細で重要ではない「ラッピング」と難読化のタイプであり、間接的な層を追加してコードをugくする以外の本当の目的はありません。あなたの上司は、取りつかれるべきより重要な問題を見つけることができると思います。

29
matt b

ロガーオブジェクトは確実に再利用されるため、どちらの方法でも追加のインスタンス化は行われません。ロガーは、各メッセージがクラスLoggerWrapper、行12から発行されたことを常に忠実に記録するため、ファイル/行番号情報が役に立たないというのが私が見ている大きな問題です:

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

12
Péter Török

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

アプリケーションが_MyLoggerWrapper.debug_メソッドを大量に呼び出す場合、これはすべて大きなパフォーマンスヒットにつながる可能性があります。また、マルチスレッドアプリケーションでは、同時実行性のボトルネックになる可能性があります。

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

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

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


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

10
Stephen C

すでに述べた理由に追加するために、上司の提案は次の理由で悪いです。

  • 何かをログに記録するたびに、ロギングとは関係のない何かを繰り返し入力する必要があります:this.getClass()
  • 静的コンテキストと非静的コンテキストの間に不均一なインターフェースを作成します(静的コンテキストにはthisがないため)
  • 追加の不必要なパラメーターはエラーの余地を作り、同じクラスのステートメントが異なるロガーに行くことを可能にします(不注意なコピー貼り付けを考えてください)
  • ロガー宣言の74文字を保存する一方で、各ロギング呼び出しに27文字の余分な文字を追加します。これは、クラスがロガーを2回以上使用する場合、文字数の点で定型コードを増やすことを意味します。
9
oksayt

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

ロガーの 'caching'は、使用されるフレームワークに依存します。しかし、それが行われたとしても、every作成するログ文の目的のロガーを検索する必要があります。メソッドに3つのステートメントがあるため、3回検索する必要があります。 static変数として使用すると、一度だけ検索する必要があります!

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

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

7
SPee

Java 8-それを行うためのインターフェースを定義してください。例えば:

package logtesting;

import Java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public interface Loggable { 
    enum LogLevel {
        TRACE, DEBUG, INFO, WARN, ERROR
    }

    LogLevel TRACE = LogLevel.TRACE;
    LogLevel DEBUG = LogLevel.DEBUG;
    LogLevel INFO = LogLevel.INFO;
    LogLevel WARN = LogLevel.WARN;
    LogLevel ERROR = LogLevel.ERROR;

    default void log(Object...args){
        log(DEBUG, args);
    }

    default void log(final LogLevel level, final Object...args){
        Logger logger = LoggerFactory.getLogger(this.getClass());
        switch(level){
        case ERROR:
            if (logger.isErrorEnabled()){
                logger.error(concat(args));
            }
            break;
        case WARN:
            if (logger.isWarnEnabled()){
                logger.warn(concat(args));
            }
            break;          
        case INFO:
            if (logger.isInfoEnabled()){
                logger.info(concat(args));
            }
        case TRACE:
            if (logger.isTraceEnabled()){
                logger.trace(concat(args));
            }
            break;
        default:
            if (logger.isDebugEnabled()){
                logger.debug(concat(args));
            }
            break;
        }
    }

    default String concat(final Object...args){ 
        return Arrays.stream(args).map(o->o.toString()).collect(Collectors.joining());
    }
}

そして、あなたがしなければならないのは、クラスがimplement Loggedを宣言していることを確認することだけです。

log(INFO, "This is the first part ","of my string ","and this ","is the last");

Log()関数は、ストリングの連結を処理しますが、使用可能かどうかをテストした後のみです。デフォルトでデバッグのためにログを記録します。デバッグのためにログを記録したい場合は、LogLevel引数を省略できます。これは非常に単純な例です。これを改善するために、個々のメソッド(error()、trace()、warn()など)を実装するなど、さまざまなことができます。ロガーを返す関数として「logger」を実装することもできます。

public interface Loggable {
    default Logger logger(){
        return LoggerFactory.getLogger(this.getClass());
    }
}

そして、あなたのロガーを使用することはかなり簡単になります:

logger().debug("This is my message");

すべてのLoggerメソッドのデリゲートメソッドを生成することで完全に機能させることもできます。これにより、実装するすべてのクラスがLoggerのインスタンスになります。

package logtesting;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;

public interface Loggable extends Logger {
    default Logger logger(){
        return LoggerFactory.getLogger(this.getClass());
    }

    default String getName() {
        return logger().getName();
    }

    default boolean isTraceEnabled() {
        return logger().isTraceEnabled();
    }

    default void trace(String msg) {
        logger().trace(msg);
    }

    default void trace(String format, Object arg) {
        logger().trace(format, arg);
    }

    default void trace(String format, Object arg1, Object arg2) {
        logger().trace(format, arg1, arg2);
    }

    default void trace(String format, Object... arguments) {
        logger().trace(format, arguments);
    }

    default void trace(String msg, Throwable t) {
        logger().trace(msg, t);
    }

    default boolean isTraceEnabled(Marker marker) {
        return logger().isTraceEnabled(marker);
    }

    default void trace(Marker marker, String msg) {
        logger().trace(marker, msg);
    }

    default void trace(Marker marker, String format, Object arg) {
        logger().trace(marker, format, arg);
    }

    default void trace(Marker marker, String format, Object arg1, Object arg2) {
        logger().trace(marker, format, arg1, arg2);
    }

    default void trace(Marker marker, String format, Object... argArray) {
        logger().trace(marker, format, argArray);
    }

    default void trace(Marker marker, String msg, Throwable t) {
        logger().trace(marker, msg, t);
    }

    default boolean isDebugEnabled() {
        return logger().isDebugEnabled();
    }

    default void debug(String msg) {
        logger().debug(msg);
    }

    default void debug(String format, Object arg) {
        logger().debug(format, arg);
    }

    default void debug(String format, Object arg1, Object arg2) {
        logger().debug(format, arg1, arg2);
    }

    default void debug(String format, Object... arguments) {
        logger().debug(format, arguments);
    }

    default void debug(String msg, Throwable t) {
        logger().debug(msg, t);
    }

    default boolean isDebugEnabled(Marker marker) {
        return logger().isDebugEnabled(marker);
    }

    default void debug(Marker marker, String msg) {
        logger().debug(marker, msg);
    }

    default void debug(Marker marker, String format, Object arg) {
        logger().debug(marker, format, arg);
    }

    default void debug(Marker marker, String format, Object arg1, Object arg2) {
        logger().debug(marker, format, arg1, arg2);
    }

    default void debug(Marker marker, String format, Object... arguments) {
        logger().debug(marker, format, arguments);
    }

    default void debug(Marker marker, String msg, Throwable t) {
        logger().debug(marker, msg, t);
    }

    default boolean isInfoEnabled() {
        return logger().isInfoEnabled();
    }

    default void info(String msg) {
        logger().info(msg);
    }

    default void info(String format, Object arg) {
        logger().info(format, arg);
    }

    default void info(String format, Object arg1, Object arg2) {
        logger().info(format, arg1, arg2);
    }

    default void info(String format, Object... arguments) {
        logger().info(format, arguments);
    }

    default void info(String msg, Throwable t) {
        logger().info(msg, t);
    }

    default boolean isInfoEnabled(Marker marker) {
        return logger().isInfoEnabled(marker);
    }

    default void info(Marker marker, String msg) {
        logger().info(marker, msg);
    }

    default void info(Marker marker, String format, Object arg) {
        logger().info(marker, format, arg);
    }

    default void info(Marker marker, String format, Object arg1, Object arg2) {
        logger().info(marker, format, arg1, arg2);
    }

    default void info(Marker marker, String format, Object... arguments) {
        logger().info(marker, format, arguments);
    }

    default void info(Marker marker, String msg, Throwable t) {
        logger().info(marker, msg, t);
    }

    default boolean isWarnEnabled() {
        return logger().isWarnEnabled();
    }

    default void warn(String msg) {
        logger().warn(msg);
    }

    default void warn(String format, Object arg) {
        logger().warn(format, arg);
    }

    default void warn(String format, Object... arguments) {
        logger().warn(format, arguments);
    }

    default void warn(String format, Object arg1, Object arg2) {
        logger().warn(format, arg1, arg2);
    }

    default void warn(String msg, Throwable t) {
        logger().warn(msg, t);
    }

    default boolean isWarnEnabled(Marker marker) {
        return logger().isWarnEnabled(marker);
    }

    default void warn(Marker marker, String msg) {
        logger().warn(marker, msg);
    }

    default void warn(Marker marker, String format, Object arg) {
        logger().warn(marker, format, arg);
    }

    default void warn(Marker marker, String format, Object arg1, Object arg2) {
        logger().warn(marker, format, arg1, arg2);
    }

    default void warn(Marker marker, String format, Object... arguments) {
        logger().warn(marker, format, arguments);
    }

    default void warn(Marker marker, String msg, Throwable t) {
        logger().warn(marker, msg, t);
    }

    default boolean isErrorEnabled() {
        return logger().isErrorEnabled();
    }

    default void error(String msg) {
        logger().error(msg);
    }

    default void error(String format, Object arg) {
        logger().error(format, arg);
    }

    default void error(String format, Object arg1, Object arg2) {
        logger().error(format, arg1, arg2);
    }

    default void error(String format, Object... arguments) {
        logger().error(format, arguments);
    }

    default void error(String msg, Throwable t) {
        logger().error(msg, t);
    }

    default boolean isErrorEnabled(Marker marker) {
        return logger().isErrorEnabled(marker);
    }

    default void error(Marker marker, String msg) {
        logger().error(marker, msg);
    }

    default void error(Marker marker, String format, Object arg) {
        logger().error(marker, format, arg);
    }

    default void error(Marker marker, String format, Object arg1, Object arg2) {
        logger().error(marker, format, arg1, arg2);
    }

    default void error(Marker marker, String format, Object... arguments) {
        logger().error(marker, format, arguments);
    }

    default void error(Marker marker, String msg, Throwable t) {
        logger().error(marker, msg, t);
    }
}

もちろん、前述したように、これは、ログを記録するたびに、LoggerFactory内でLoggerルックアッププロセスを実行する必要があることを意味します-logger()メソッドをオーバーライドしない限り... 「推奨」方法。

5
Steve K

推奨されるパターンは、読みやすく、実装が最も簡単だと言わざるを得ません。私はそれから迷う理由を見つけません。特にメリットはありません。

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

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

4
Alasdair

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

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

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

4
Mariano LEANCE

回答の2015年版: lombok @ slf4j で心を解放してください。

4
fayndee

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

Slf4jの上に構築される独自の抽象化を含むJetty Webコンテナーを見てみるとよいでしょう。非常に素晴らしい。

あなたのボスのアプローチが彼が考えるものを達成しない2つの理由があります。

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

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

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

また、コードは追加の間接参照を行っています。LoggerFactory.getLogger(Class)は_Class#getName_を使用し、LoggerFactory.getLogger(String)にデリゲートします。

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

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

1
toolforger

私は以前のコメントの1つでそれを見逃したかもしれませんが、ロガーが静的であるという言及を見ていませんでした、LoggerFactoryの呼び出しは1回行われます(クラスのインスタンス化ごと)-ロガーを作成するための複数の呼び出しに関する最初の懸念は間違っています。

ラップクラスの追加に関するすべての問題に関する他のコメントも非常に重要です。

0
Jay