具有延迟评估的String.format

时间:2009-06-03 06:36:27

标签: java logging lazy-evaluation

我需要类似于String.format(...)方法的东西,但需要进行懒惰评估。

这个lazyFormat方法应该返回一些对象,其toString()方法将评估格式模式。

我怀疑有人已经这样做了。这可以在任何图书馆中使用吗?

我想替换它(logger是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来格式化字符串。

10 个答案:

答案 0 :(得分:22)

如果您正在寻找“简单”的解决方案:

 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);
            }
        };
    }
}

输出:

  

一些文字与字符串连在一起   模式另一个loooong字符串

如果愿意,您当然可以在lazyFormat中添加任何isDebugEnabled()语句。

答案 1 :(得分:16)

可以通过在最新的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()); 
} 
  

重复这样做会产生效果   代码感觉它更像是关于日志而不是手头的实际任务。   此外,它会导致两次检查日志记录级别;一旦   在调用isDebugEnabled和调用方法一次。一个更好的   替代方案是:

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

使用高于日志记录级别的代码   只会检查一次,并且只会发生字符串构造   启用调试日志记录时。

答案 2 :(得分:13)

如果您为了高效日志记录而寻找延迟连接,请查看Slf4J 这允许你写:

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

只有在设置了调试级别时才会进行字符串连接。

答案 3 :(得分:5)

重要说明:强烈建议将所有日志记录代码移至SLF4J(尤其是log4j 1.x)。它可以保护您免受任何特定日志记录实现的特殊问题(即错误)的困扰。它不仅具有针对众所周知的后端实现问题的“修复”,而且还适用于多年来出现的更快的更快实现。


直接回答您的问题,这里使用SLF4J看起来像是什么:

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

您提供的最重要的一点是您传递两个Object实例。不会立即评估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();}});

我现在已经重构了我的所有日​​志代码以遵循这个简单的模型。它已经大大整理了一切。现在当我看到任何不使用它的日志代码时,我重构日志代码以使用这个新模式,即使它还需要。这样,如果稍后进行更改需要添加一些“昂贵”的操作,那么基础架构样板已经在那里简化了添加操作的任务。

答案 4 :(得分:3)

Andreas' answer的基础上,如果Logger.isDebugEnabled返回true,我可以考虑采用两种方法解决仅执行格式化的问题:

选项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 1}}。这将停止日志消息的格式化,并且只会发送一些“虚拟”信息。

因此记录器的用例可能是:

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;
  }
}

在此方法中,预计logger方法中会显示lazyFormat。这种方法的好处是调用者在调用isDebugEnabled时不需要检查lazyFormat方法,因此典型用法可以是:

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

答案 5 :(得分:3)

您可以将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)

答案 6 :(得分:2)

在Log4j中引入1.2.16是两个可以为你完成此任务的类。

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); 

答案 7 :(得分:1)

或者你可以把它写成

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));
}

答案 8 :(得分:1)

如果您更喜欢String.format语法而不是{0}语法,并且可以使用 Java 8 / JDK 8 ,则可以使用lambdas / Suppliers:

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

()->...在这里充当供应商,并将被懒惰地评估。

答案 9 :(得分:0)

您可以定义一个包装器,以便仅在需要时调用String.format()

有关详细的代码示例,请参阅this question

同样的问题还有一个variadic function example,正如安德烈亚斯的答案中所建议的那样。