Image by Kevin Phillips from Pixabay

Be careful when creating log statements. Even though the log doesn’t actually log your input doesn’t mean it doesn’t evaluate the log statement.

For example:

log.debug("I found {} and {}", getone(), gettwo());

Seems, pretty good. A debug log with two parameters into the String.

So all is well?

Not really.

You see the two inputs into the log:

getone()

gettwo()

These two methods are ALWAYS evaluated. Meaning, even though the log statement may not be used, we are still calculating all the inputs to the log call.

If these calls are expensive we are potentially wasting a large amount of CPU cycles for nothing.

A typical workaround would be something like this:

if (log.isDebugEnabled()) {

log.debug("I found {} and {}", getone(), gettwo());

}

But this is fairly ugly. Isn’t the whole point of the log.debug to ensure our code is only logged if debugging is enabled?

What we want is a single line debugger statement and only evaluate the input parameters if the logger is enabled.

To solve similar situations we would use the supplier pattern of Java 8. The supplier will only calculate the value when it is needed.

Unfortunately, most logging frameworks don’t support the supplier pattern.

At least directly.

In reality, the logger isn’t expecting a string for the parameters. It actually accepts any object. The logger will invoke the toString method on the object to translate it into a string. The trick here is the logger will ONLY call toString if it needs to, ie if logging is enabled.

So we simply need to wrap our supplier with an object that invokes the supplier on a call to toString.

import java.util.function.Supplier;



public class LazyString {

private final Supplier<?> stringSupplier;



public static LazyString lazy(Supplier<?> stringSupplier) {

return new LazyString(stringSupplier);

}



public LazyString(final Supplier<?> stringSupplier) {

this.stringSupplier = stringSupplier;

}



@Override

public String toString() {

return String.valueOf(stringSupplier.get());

}

}

We can now update our log statement to look like this:

import static LazyString.lazy; log.debug("I found {} and {}", lazy(this::getone), lazy(this::gettwo));

Now ideally the log frameworks would support suppliers directly. But until they do this is an acceptable workaround.

For full code examples check out my github repo: