Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy evaluation for logging in Java 8

Tags:

When you have values than are expensive to compute, a common pattern you see in logging frameworks is

if (log.isDebugEnabled()) {
    String value = expensiveComputation();
    log.debug("value: {}", value);
}

Since Java 8 added lambdas, it'd be nice to do:

log.debug("value: {}", (Supplier<String>) this::expensiveComputation);

Which almost works because the logging framework will do toString() on the parameter. The problem is toString() on Supplier is the implementation in Object.

Is there a way to supply something that's evaluated lazily to Logger methods? It would almost just be a Supplier with a default toString() that calls get().

like image 471
David Ehrmann Avatar asked Nov 01 '17 20:11

David Ehrmann


People also ask

Is there lazy evaluation in Java?

Lazy EvaluationsWhile Java uses lazy or normal order when evaluating logical operators, it uses an eager or applicative order when evaluating method arguments. All the arguments to methods are fully evaluated before a method is invoked.

Are Java 8 streams lazy?

The Java 8 Streams API is fully based on the 'process only on demand' strategy and hence supports laziness. In the Java 8 Streams API, the intermediate operations are lazy and their internal processing model is optimised to make it being capable of processing the large amount of data with high performance.

Which logging framework is best for Java?

One of the most popular solutions for the Java world is the Apache Log4j 2 framework. Maintained by the Apache Foundation, Log4j 2 is an improvement on the original Log4j, which was the most popular logging framework in Java for many years.

What is Java lazy?

Lazy class loading is an important feature of the Java runtime environment as it can reduce memory usage under certain circumstances. For example, if a part of a program never is executed during a session, classes referenced only in that part of the program never will be loaded.


2 Answers

To pass an argument that will executed in a lazy way the String computation, you have to pass a Supplier and not a String.
The method that you invoke should have this signature :

void debug(Supplier<?> msgSupplier, Throwable t)

You could introduce this utility method in your own utility class.
But you should not need to do that as recent logging frameworks such as Log4j2 provides this feature out of the box.

For example, org.apache.logging.log4j.Logger provides overloaded methods to log that accept a Supplier.
For example :

void debug(MessageSupplier msgSupplier, Throwable t)

Logs a message (only to be constructed if the logging level is the DEBUG level) including the stack trace of the Throwable t passed as parameter. The MessageSupplier may or may not use the MessageFactory to construct the Message.

Parameters:

msgSupplier - A function, which when called, produces the desired log message.

t - the exception to log, including its stack trace.

From Log4j2 documentation :

Java 8 lambda support for lazy logging

In release 2.4, the Logger interface added support for lambda expressions. This allows client code to lazily log messages without explicitly checking if the requested log level is enabled. For example, previously you would write:

if (logger.isTraceEnabled()) {
    logger.trace("Some long-running operation returned {}", expensiveOperation());
}

With Java 8 you can achieve the same effect with a lambda expression. You no longer need to explicitly check the log level:

logger.trace("Some long-running operation returned {}", 
              () ->    expensiveOperation());
like image 152
davidxxx Avatar answered Oct 15 '22 04:10

davidxxx


A small helper object will allow you to do what you want:

public class MessageSupplier {
    private Supplier<?> supplier;

    public MessageSupplier(Supplier<?> supplier) {
        this.supplier = supplier;
    }

    @Override
    public String toString() {
        return supplier.get().toString();
    }

    public static MessageSupplier msg(Supplier<?> supplier) {
        return new MessageSupplier(supplier);
    }
}

Then, with a static import of msg:

log.debug("foo: {}", msg(this::expensiveComputation));
like image 38
teppic Avatar answered Oct 15 '22 06:10

teppic