我需要类似于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来格式化字符串。
答案 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,正如安德烈亚斯的答案中所建议的那样。