如何用Java8s lambdas改进日志记录机制

时间:2014-02-13 10:46:31

标签: java logging lambda java-8

如何通过不减少字符串连接的开销来改进日志记录机制是多么可能?

考虑以下示例:

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

在具有大量调试语句的大型应用程序中,这是一个很大的性能问题。

那么,如何解决这个问题?

5 个答案:

答案 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表达式的支持,这些表达式对你的情况非常有用(以及其他答案已经手动复制):

来自https://garygregory.wordpress.com/2015/09/16/a-gentle-introduction-to-the-log4j-api-and-lambda-basics/

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

使用格式StringSupplier<String>数组。这样,除非日志记录实际上是可发布的,否则不会调用toString方法。这样你就不必担心有关登录应用程序代码的丑陋if语句。