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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21751954/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 10:29:15  来源:igfitidea点击:

How to improve logging mechanism with Java8s lambdas

javalogginglambdajava-8

提问by bobbel

How is it possible, to improve your logging mechanism, by not having the overhead of string concatenations?

如何通过没有字符串连接的开销来改进您的日志记录机制?

Consider the following example:

考虑以下示例:

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;
    }
}

The log method log.fine(...)will not log anything, because the log level is set to INFO. The problem is, that the method getValuewill be evaluated anyway.

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

And this is a big performance issue in big applications with a lot of debug statements.

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

So, how to solve this problem?

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

回答by bobbel

Since Java8 it is possible to use the new introduced lambda expressionsfor this scenario.

从 Java8 开始,可以在这种情况下使用新引入的lambda 表达式

Here is a modified example of the logging:

这是日志记录的修改示例:

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

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();
            }
        }
    }
}

With this modification you can improve the performance of your applications a lot, if you have many log statements, which are only for debugging purposes.

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

Of course you can use any Logger you want. This is only an example of the java.util.Logger.

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

回答by Stephen C

@bobbel has explained how to do it.

@bobbel 已经解释了如何去做。

I'd like to add that while this represents a performance improvement over your original code, the classic way of dealing with this is still faster:

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

if (log.isLoggable(Level.FINE)) {
    log.fine("Trace value: " + getValue());
}

and only marginally more verbose / wordy.

并且只是稍微冗长/罗嗦。

The reason it is faster is that the lambda version has the additional runtime overheads of creating the callable instance (capture cost), and an extra level of method calls.

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

And finally, there is the issue of creating the LambdaLoggerinstances. @bobbel's code shows this being done using a constructor, but in reality java.util.logging.Loggerobjects need to be created by a factory method to avoid proliferation of objects. That implies a bunch of extra infrastructure (and code changes) to get this to work with a custom subclass of Logger.

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

回答by aepurniet

use a format String, and an array of Supplier<String>. this way no toStringmethods are called unless the the log record is actually publishable. this way you dont have to bother with ugly ifstatements about logging in application code.

使用格式String和一个数组Supplier<String>。这样,toString除非日志记录实际上是可发布的,否则不会调用任何方法。这样您就不必为if有关登录应用程序代码的丑陋语句而烦恼。

回答by mauretto

Just create wrapper methods for your current logger as:

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

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

and use it:

并使用它:

info(log, () -> "x: " + x + ", y: " + y);

Reference: JAVA SE 8 for the Really Impatient eBook, pages 48-49.

参考:JAVA SE 8 的真正不耐烦的电子书,第 48-49 页。

回答by jhyot

Apparently Log4j 2.4 includes support for lambda expressions which are exactly useful for your case (and which other answers have replicated manually):

显然,Log4j 2.4 包括对 lambda 表达式的支持,这对您的案例非常有用(以及其他手动复制的答案):

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

来自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());