如何通过不减少字符串连接的开销来改进日志记录机制是多么可能?
考虑以下示例:
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggerTest {
public static void main(String[] args) {
// get logger
Logger log = Logger.getLogger(LoggerTest.class.getName());
// set log level to INFO (so fine will not be logged)
log.setLevel(Level.INFO);
// this line won't log anything, but will evaluate the getValue method
log.fine("Trace value: " + getValue());
}
// example method to get a value with a lot of string concatenation
private static String getValue() {
String val = "";
for (int i = 0; i < 1000; i++) {
val += "foo";
}
return val;
}
}
日志方法log.fine(...)
不会记录任何内容,因为日志级别设置为INFO
。问题是,无论如何都会评估方法getValue
。
在具有大量调试语句的大型应用程序中,这是一个很大的性能问题。
那么,如何解决这个问题?
答案 0 :(得分:11)
从Java8开始,可以在这种情况下使用新引入的lambda expressions。
以下是日志记录的修改示例:
<强> LoggerTest.class 强>
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggerTest {
public static void main(String[] args) {
// get own lambda logger
LambdaLogger log = new LambdaLogger(LoggerTest.class.getName());
// set log level to INFO (so fine will not be logged)
log.setLevel(Level.INFO);
// this line won't log anything, and will also not evaluate the getValue method!
log.fine(()-> "Trace value: " + getValue()); // changed to lambda expression
}
// example method to get a value with a lot of string concatenation
private static String getValue() {
String val = "";
for (int i = 0; i < 1000; i++) {
val += "foo";
}
return val;
}
}
<强> LambdaLogger.class 强>
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LambdaLogger extends Logger {
public LambdaLogger(String name) {
super(name, null);
}
public void fine(Callable<String> message) {
// log only, if it's loggable
if (isLoggable(Level.FINE)) {
try {
// evaluate here the callable method
super.fine(message.call());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
通过此修改,如果您有许多日志语句(仅用于调试目的),则可以大大提高应用程序的性能。
当然,您可以使用任何您想要的记录器。这只是java.util.Logger
。
答案 1 :(得分:8)
@bobbel解释了如何做到这一点。
我想补充一点,虽然这代表了对原始代码的性能提升,但处理此问题的经典方法仍然更快:
if (log.isLoggable(Level.FINE)) {
log.fine("Trace value: " + getValue());
}
并且只是略微冗长/冗长。
它更快的原因是lambda版本具有创建可调用实例(捕获成本)的额外运行时开销,以及额外级别的方法调用。
最后,存在创建LambdaLogger
实例的问题。 @ bobbel的代码显示这是使用构造函数完成的,但实际上java.util.logging.Logger
对象需要通过工厂方法创建,以避免对象的扩散。这意味着需要一大堆额外的基础设施(以及代码更改)才能使其与Logger
的自定义子类一起使用。
答案 2 :(得分:6)
显然Log4j 2.4包含对lambda表达式的支持,这些表达式对你的情况非常有用(以及其他答案已经手动复制):
// Uses Java 8 lambdas to build arguments on demand
logger.debug("I am logging that {} happened.", () -> compute());
答案 3 :(得分:5)
只需为当前记录器创建包装器方法:
public static void info(Logger logger, Supplier<String> message) {
if (logger.isLoggable(Level.INFO))
logger.info(message.get());
}
并使用它:
info(log, () -> "x: " + x + ", y: " + y);
参考:JAVA SE 8 for the Really Impatient eBook,第48-49页。
答案 4 :(得分:1)
使用格式String
和Supplier<String>
数组。这样,除非日志记录实际上是可发布的,否则不会调用toString
方法。这样你就不必担心有关登录应用程序代码的丑陋if
语句。