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

bob*_*bel 9 java lambda logging 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

log方法log.fine(...)不会记录任何内容,因为日志级别设置为INFO.问题是,getValue无论如何都要评估该方法.

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

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

bob*_*bel 12

从Java8开始,可以在此方案中使用新引入的lambda表达式.

以下是日志记录的修改示例:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您有许多日志语句(仅用于调试目的),通过此修改,您可以大大提高应用程序的性能.

当然,您可以使用任何您想要的记录器.这只是一个例子java.util.Logger.

  • 讨论该视频中捕获成本的一个很好的起点(跳过所有不错的背景信息)将在[36:25] [1]左右.[1]:http://youtu.be/C_QbkGU_lqY?t = 36m25s (2认同)

Ste*_*n C 9

@bobbel解释了如何做到这一点.

我想补充一点,虽然这代表了对原始代码的性能提升,但处理此问题的经典方法仍然更快:

if (log.isLoggable(Level.FINE)) {
    log.fine("Trace value: " + getValue());
}
Run Code Online (Sandbox Code Playgroud)

并且只是略微冗长/冗长.

它更快的原因是lambda版本具有创建可调用实例(捕获成本)的额外运行时开销,以及额外级别的方法调用.

最后,还有创建LambdaLogger实例的问题.@ bobbel的代码显示这是使用构造函数完成的,但实际上java.util.logging.Logger对象需要通过工厂方法创建,以避免对象的扩散.这意味着需要一大堆额外的基础设施(和代码更改)才能使其与自定义子类一起使用Logger.

  • +1添加"if"仍然是最好的. (2认同)
  • @bobel - 抱歉.不,我不喜欢它.我不接受有必要"保护"大多数日志记录调用,或者您通常应首先在代码中进行许多"精细"日志记录调用. (2认同)

jhy*_*yot 7

显然,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());
Run Code Online (Sandbox Code Playgroud)

  • slf4j是否在log4j的api上有包装 (3认同)

mau*_*tto 6

只需为当前记录器创建包装器方法:

public static void info(Logger logger, Supplier<String> message) {
    if (logger.isLoggable(Level.INFO))
    logger.info(message.get());
}
Run Code Online (Sandbox Code Playgroud)

并使用它:

info(log, () -> "x: " + x + ", y: " + y);
Run Code Online (Sandbox Code Playgroud)

参考:JAVA SE 8 for the Really Impatient eBook,第48-49页.

  • 在Java 8中,java.util.logging.Logger具有Logger.info(Supplier &lt;String&gt;)方法,那么当您可以直接使用此Logger方法时,为什么还要编写包装器呢? (2认同)