web-dev-qa-db-ja.com

遅延評価を使用したString.format

String.format(...) メソッドに似たものが必要ですが、遅延評価が必要です。

このlazyFormatメソッドは、toString()メソッドがフォーマットパターンを評価するオブジェクトを返す必要があります。

誰かがすでにこれをしているのではないかと思います。これはどの図書館でも利用できますか?

これを置き換えたい(ロガーはlog4jインスタンスです):

if(logger.isDebugEnabled() ) {
   logger.debug(String.format("some texts %s with patterns %s", object1, object2));
}

これとともに:

logger.debug(lazyFormat("some texts %s with patterns %s", object1, object2));

デバッグログが有効になっている場合にのみ文字列をフォーマットするためにlazyFormatが必要です。

22
Juha Syrjälä

「シンプルな」ソリューションをお探しの場合:

_ public class LazyFormat {

    public static void main(String[] args) {
        Object o = lazyFormat("some texts %s with patterns %s", "looong string", "another loooong string");
        System.out.println(o);
    }

    private static Object lazyFormat(final String s, final Object... o) {
        return new Object() {
            @Override
            public String toString() {
                return String.format(s,o);
            }
        };
    }
}
_

出力:

いくつかのテキストはパターンのある文字列を失います別の文字列は

もちろん、必要に応じて、lazyFormat内に任意のisDebugEnabled()ステートメントを追加できます。

22

これは、最新のlog4j 2.Xバージョンのパラメーター置換を使用して実行できます http://logging.Apache.org/log4j/2.x/log4j-users-guide.pdf

4.1.1.2パラメータの置換

多くの場合、ロギングの目的は、システムで何が起こっているかに関する情報を提供することです。これには、操作されているオブジェクトに関する情報を含める必要があります。 Log4j 1.xでは、これは次の方法で実行できます。

if (logger.isDebugEnabled()) {     
  logger.debug("Logging in user " + user.getName() + " with id " + user.getId()); 
} 

これを繰り返し行うと、実際のタスクよりもロギングに関するものであるかのようにコードを感じさせる効果があります。さらに、ログレベルが2回チェックされます。 isDebugEnabledの呼び出しで1回、debugメソッドで1回。より良い代替案は次のとおりです。

logger.debug("Logging in user {} with id {}", user.getName(), user.getId()); 

上記のコードでは、ロギングレベルは一度だけチェックされ、文字列の構築はデバッグロギングが有効になっている場合にのみ発生します。

17
yurilo

効率的なロギングのために遅延連結を探している場合は、 Slf4J を見てください。これにより、次のように記述できます。

LOGGER.debug("this is my long string {}", fatObject);

文字列の連結は、デバッグレベルが設定されている場合にのみ行われます。

13

重要な注意:すべてのロギングコードを移動して SLF4J (特にlog4j 1.x)を使用することを強くお勧めします。これにより、特定のロギング実装でのあらゆる種類の特異な問題(バグなど)に悩まされることから保護されます。よく知られているバックエンド実装の問題に対する「修正」があるだけでなく、長年にわたって出現した新しいより高速な実装でも機能します。


あなたの質問に直接答えて、ここでそれがどのように見えるか SLF4J

_LOGGER.debug("some texts {} with patterns {}", object1, object2);
_

あなたが提供したものの最も重要な部分は、2つのオブジェクトインスタンスを渡すという事実です。 object1.toString()メソッドとobject2.toString()メソッドはすぐには評価されません。さらに重要なことに、toString()メソッドは、返されるデータが実際に使用される場合にのみ評価されます。つまり、遅延評価の本当の意味です。

大量のクラスでtoString()をオーバーライドする必要のない、より一般的なパターンを考えようとしました(オーバーライドを実行するためのアクセス権がないクラスもあります)。簡単なドロップインプレースソリューションを思いつきました。繰り返しますが、 SLF4J を使用して、レベルのロギングが有効になっている場合にのみ文字列を作成します。これが私のコードです:

_    class SimpleSfl4jLazyStringEvaluation {
      private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSfl4jLazyStringEvaluation.class);

      ...

      public void someCodeSomewhereInTheClass() {
//all the code between here
        LOGGER.debug(
            "{}"
          , new Object() {
              @Override
              public String toString() {
                return "someExpensiveInternalState=" + getSomeExpensiveInternalState();
              }
            }
//and here can be turned into a one liner
        );
      }

      private String getSomeExpensiveInternalState() {
        //do expensive string generation/concatenation here
      }
    }
_

また、one-linerに簡略化するために、someCodeSomewhereInTheClass()のLOGGER行を次のように短縮できます。

_LOGGER.debug("{}", new Object(){@Override public String toString(){return "someExpensiveInternalState=" + getSomeExpensiveInternalState();}});
_

この単純なモデルに従うように、すべてのロギングコードをリファクタリングしました。それは物事をかなり片付けました。そして、これを使用しないロギングコードを見つけたら、まだ必要な場合でも、この新しいパターンを使用するようにロギングコードをリファクタリングします。そうすれば、後で変更を加えて「高価な」操作を追加する必要がある場合、インフラストラクチャの定型文がすでに存在しているため、操作を追加するだけでタスクが簡素化されます。

5

Log4Jロガーインスタンスを独自のJava5互換/String.format互換クラス内にラップすることができます。何かのようなもの:

public class Log4jWrapper {

    private final Logger inner;

    private Log4jWrapper(Class<?> clazz) {
        inner = Logger.getLogger(clazz);
    }

    public static Log4jWrapper getLogger(Class<?> clazz) {
        return new Log4jWrapper(clazz);
    }

    public void trace(String format, Object... args) {
        if(inner.isTraceEnabled()) {
            inner.trace(String.format(format, args));    
        }
    }

    public void debug(String format, Object... args) {
        if(inner.isDebugEnabled()) {
            inner.debug(String.format(format, args));    
        }
    }

    public void warn(String format, Object... args) {
        inner.warn(String.format(format, args));    
    }

    public void error(String format, Object... args) {
        inner.error(String.format(format, args));    
    }

    public void fatal(String format, Object... args) {
        inner.fatal(String.format(format, args));    
    }    
}

ラッパーを使用するには、ロガーフィールド宣言を次のように変更します。

private final static Log4jWrapper logger = Log4jWrapper.getLogger(ClassUsingLogging.class);

ラッパークラスにはいくつかの追加メソッドが必要です。たとえば、現在、ロギング例外(つまり、logger.debug(message、exception))を処理していませんが、これを追加するのは難しいことではありません。

このクラスの使用は、文字列がフォーマットされていることを除いて、log4jとほぼ同じです。

logger.debug("User {0} is not authorized to access function {1}", user, accessFunction)
3

Andreasの回答 に基づいて、Logger.isDebugEnabledtrueを返す場合にのみフォーマットを実行するという問題に対するいくつかのアプローチを考えることができます。

オプション1:「フォーマットを行う」フラグを渡す

1つのオプションは、実際にフォーマットを実行するかどうかを指示するメソッド引数を持つことです。ユースケースは次のとおりです。

System.out.println(lazyFormat(true, "Hello, %s.", "Bob"));
System.out.println(lazyFormat(false, "Hello, %s.", "Dave"));

出力は次のようになります。

Hello, Bob.
null

lazyFormatのコードは次のとおりです。

private String lazyFormat(boolean format, final String s, final Object... o) {
  if (format) {
    return String.format(s, o);
  }
  else {
    return null;
  }
}

この場合、String.formatは、formatフラグがtrueに設定されている場合にのみ実行され、falseに設定されている場合は、nullを返します。これにより、ログメッセージのフォーマットが停止し、「ダミー」情報が送信されます。

したがって、ロガーのユースケースは次のようになります。

logger.debug(lazyFormat(logger.isDebugEnabled(), "Message: %s", someValue));

この方法は、質問で求められているフォーマットに正確には適合しません。

オプション2:ロガーを確認します

別のアプローチは、ロガーにisDebugEnabledかどうかを直接尋ねることです。

private static String lazyFormat(final String s, final Object... o) {
  if (logger.isDebugEnabled()) {
    return String.format(s, o);
  }
  else {
    return null;
  }
}

このアプローチでは、loggerlazyFormatメソッドに表示されることが期待されます。また、このアプローチの利点は、isDebugEnabledが呼び出されたときに、呼び出し元がlazyFormatメソッドをチェックする必要がないことです。したがって、通常の使用法は次のとおりです。

logger.debug(lazyFormat("Debug message is %s", someMessage));
3
coobird

Log4j 1.2.16で導入されたのは、これを行う2つのクラスです。

org.Apache.log4j.LogMFはメッセージのフォーマットにJava.text.MessageFormatを使用し、org.Apache.log4j.LogSFは「SLF4Jパターン構文」を使用して高速であると言われています。

次に例を示します。

LogSF.debug(log, "Processing request {}", req);

そして

 LogMF.debug(logger, "The {0} jumped over the moon {1} times", "cow", 5); 
2
Victor

またはあなたはそれを次のように書くことができます

debug(logger, "some texts %s with patterns %s", object1, object2);

public static void debug(Logger logger, String format, Object... args) {
    if(logger.isDebugEnabled()) 
       logger.debug(String.format("some texts %s with patterns %s", args));
}
1
Peter Lawrey

{0}構文よりもString.format構文の方が好きで、Java 8/JDK 8を使用できる場合は、ラムダ/サプライヤーを使用できます。

logger.log(Level.FINER, () -> String.format("SomeOperation %s took %04dms to complete", name, duration));

()->...ここではサプライヤーとして機能し、怠惰に評価されます。

1
smakks

必要な場合にのみString.format()を呼び出すために、ラッパーを定義できます。

詳細なコード例については、 この質問 を参照してください。

アンドレアスの回答で示唆されているように、同じ質問にも 可変個引数関数の例 があります。

0
VonC