24

我需要类似于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 格式化字符串。

4

10 回答 10

21

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

 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 带有模式的字符串 另一个 loooong 字符串

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

于 2009-06-03T06:46:17.750 回答
17

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

使用上面的代码,日志级别只会检查一次,并且只有在启用调试日志时才会发生字符串构造。

于 2012-12-29T05:16:13.653 回答
13

如果您正在寻找延迟连接以实现高效日志记录,请查看Slf4J ,它允许您编写:

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

只有设置了调试级别,才会发生字符串连接。

于 2009-06-03T06:40:07.357 回答
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();}});

我现在已经重构了我所有的日志记录代码以遵循这个简单的模型。它已经大大整理了一些东西。现在,当我看到任何不使用它的日志记录代码时,我会重构日志记录代码以使用这个新模式,即使它还需要它。这样,如果/当稍后进行更改以需要添加一些“昂贵的”操作时,基础设施样板已经存在,将任务简化为仅添加操作。

于 2013-08-19T15:33:01.163 回答
3

基于Andreas 的回答,我可以想到几种方法来解决仅在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才会执行,如果设置为,它将返回一个。这将停止记录消息的格式化,并且只会发送一些“虚拟”信息。formattruefalsenull

所以记录器的用例可能是:

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));
于 2009-06-03T07:24:00.717 回答
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)
于 2009-06-03T13:28:25.923 回答
2

Log4j 1.2.16 中引入了两个可以为您执行此操作的类。

org.apache.log4j.LogMF它使用java.text.MessageFormatfor 格式发送消息并org.apache.log4j.LogSF使用“SLF4J 模式语法”,据说速度更快。

以下是示例:

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

 LogMF.debug(logger, "The {0} jumped over the moon {1} times", "cow", 5); 
于 2014-02-23T14:08:56.413 回答
2

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

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

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

于 2017-06-14T13:36:49.960 回答
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));
}
于 2009-06-03T20:59:48.727 回答
0

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

有关详细的代码示例,请参阅此问题

正如安德烈亚斯的回答所建议的,同样的问题也有一个可变参数函数示例。

于 2009-06-03T06:47:34.830 回答