How does Log4j 2.x implement lazy argument evaluation?
Asked Answered
R

3

21

Given the Java argument evaluation mechanism, how does Log4j 2.x implement lazy evaluation when formatting the message with curly brackets "to avoid the cost of parameter construction" when log is disabled?

e.g.

logger.debug("Entry number: {} is {}", i, entry[i]);
Reggi answered 24/8, 2015 at 13:47 Comment(0)
D
31

I guess what Log4j means, is that with the curly brackets, they avoid constructing a string when its not necessary (e.g. the Level is not Debug):

With

logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

the complete message is always computed even when it will not be logged.

With

logger.debug("Entry number: {} is {}", i, entry[i]);

Log4j can check the Log-Level first and then decide if its worth to construct the message string. This saves resources needed for string concatenation. It will only call toString() on the objects supplied if the message is actually being created, saving further computation costs.

Log4j uses an internal class (org.apache.logging.log4j.message.ParameterFormatter) that replaces every {} with the arguments provided. It will also catch exceptions thrown by toString() and report these failures.

Delinda answered 24/8, 2015 at 14:2 Comment(3)
Clear. So it just postpones String construction and not argument evaluation.Reggi
This answer is about slf4j and doesn't apply to vanilla log4j in question (at least to log4j 1.x). While it's possible to use slf4j API to proxy to log4j implementation, the difference between them should be stated clearer. slf4j classes are by no means "internal" to log4j. Have a look at Log4j Implicit String Formatting for more correct answers.Moody
@Moody Thanks for the hint, back then i must had SLF4j on my classpath and went down the wrong rabbit hole. As of the example in the Question and the link to log4j2 the Question is about log4j2, not log4j 1.x. I'v updated the Question, to make it clearer.Delinda
X
7

To avoid argument evaluation, just wrap it in lambda:

logger.debug(() -> { 
   "Entry number: " + i + " is " + String.valueOf(entry[i])
});

In this form, the supplier will be called for construction only before actual logging. Think of it like a function declaration.

X answered 3/3, 2020 at 13:20 Comment(1)
This is worth an upvote as it is a valid option too, although the accepted answer is closer to answering the question that was asked.Dulcinea
J
0

In release 2.4, the Logger interface added support for lambda expressions. Such that, Log4j2 loggers can also take in a message supplier that will be lazily evaluated, here is the signature for the info one:

@Override
public void info(final Supplier<?> messageSupplier) {
    logIfEnabled(FQCN, Level.INFO, null, messageSupplier, (Throwable) null);
}

This allows client code to lazily log messages without explicitly checking if the requested log level is enabled. For example, previously you would write:

// pre-Java 8 style optimization: explicitly check the log level
// to make sure the expensiveOperation() method is only called if necessary
if (logger.isTraceEnabled()) {
    logger.trace("Some long-running operation returned {}", expensiveOperation());
}

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

// Java-8 style optimization: no need to explicitly check the log level:
// the lambda expression is not evaluated if the TRACE level is not enabled
logger.trace("Some long-running operation returned {}", () -> expensiveOperation());

For Kotlin, there is a Kotlin logging facade based on Log4j 2 in which the signature is as follow:

fun info(supplier: () -> Any?) {
    delegate.logIfEnabled(FQCN, Level.INFO, null, supplier.asLog4jSupplier(), null)
}

And here is a usage example:

logger.info { "This is a log event" }

Both will avoid the cost of parameter construction.

Jyoti answered 25/1, 2023 at 1:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.