问题
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()
.
回答1:
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());
回答2:
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));
回答3:
Which almost works because the logging framework will do
toString()
on the parameter.
This statement isn't correct. If you step into the debug
/info
/whatever method, you'll find this implementation:
public void log(Level level, Supplier<String> msgSupplier) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msgSupplier.get());
doLog(lr);
}
If the level
isn't met, the Supplier
isn't even used.
回答4:
Interestingly you can't even use something like this
interface LazyString {
String toString();
}
as a functional interface
The only way I found so far is via anonymous classes.
Object o = new Object() {
@Override
public String toString() {
return myExpensiveComputation();
}
};
System.out.printf("%s", o);
回答5:
For java.util.logging
and Java 8+ you can also use this lazy and handy notation:
LOGGER.fine(() -> "Message1: " + longComputation1() + ". Message2: " + longComputation2());
longComputation1()
and longComputation2()
will be called lazy - i.e. only when needed.
来源:https://stackoverflow.com/questions/47063232/lazy-evaluation-for-logging-in-java-8