244

I am using Log4J in my application for logging. Previously I was using debug call like:

Option 1:

logger.debug("some debug text");

but some links suggest that it is better to check isDebugEnabled() first, like:

Option 2:

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text");
}

So my question is "Does option 2 improve performance any way?".

Because in any case Log4J framework have same check for debugEnabled. For option 2 it might be beneficial if we are using multiple debug statement in single method or class, where the framework does not need to call isDebugEnabled() method multiple times (on each call); in this case it calls isDebugEnabled() method only once, and if Log4J is configured to debug level then actually it calls isDebugEnabled() method twice:

  1. In case of assigning value to debugEnabled variable, and
  2. Actually called by logger.debug() method.

I don't think that if we write multiple logger.debug() statement in method or class and calling debug() method according to option 1 then it is overhead for Log4J framework in comparison with option 2. Since isDebugEnabled() is a very small method (in terms of code), it might be good candidate for inlining.

Michael Myers
  • 188,989
  • 46
  • 291
  • 292
Silent Warrior
  • 5,069
  • 8
  • 41
  • 52

16 Answers16

293

In this particular case, Option 1 is better.

The guard statement (checking isDebugEnabled()) is there to prevent potentially expensive computation of the log message when it involves invocation of the toString() methods of various objects and concatenating the results.

In the given example, the log message is a constant string, so letting the logger discard it is just as efficient as checking whether the logger is enabled, and it lowers the complexity of the code because there are fewer branches.

Better yet is to use a more up-to-date logging framework where the log statements take a format specification and a list of arguments to be substituted by the logger—but "lazily," only if the logger is enabled. This is the approach taken by slf4j.

See my answer to a related question for more information, and an example of doing something like this with log4j.

Community
  • 1
  • 1
erickson
  • 265,237
  • 58
  • 395
  • 493
  • 3
    log5j extends log4j in much the same way as slf4j – Bill Michell Jun 08 '09 at 09:06
  • This is also the approach of java.util.Logging. – Paul Jun 14 '11 at 20:07
  • @Geek It's more efficient when the log event is disabled because the log level is set high. See the section titled "The need for conditional logging" in my [answer here.](http://stackoverflow.com/questions/105852/conditional-logging-with-minimal-cyclomatic-complexity/105908#105908) – erickson Jul 24 '13 at 17:42
  • @erickson When the log level is too high you are not even doing to `log.log(level, String.format(formatter, args))` which is fine. I understand that part but I specifically do not understand the need of `String.format()` . Does it not do `toString()` computations? – Geek Jul 24 '13 at 17:56
  • No, I'm not suggesting that you invoke `String.format(...)` in your application; the logging framework does this internally, but only if the log statement is enabled. I will reword my answer to minimize confusion. – erickson Jul 24 '13 at 19:49
  • @erikson Are getters or other code in the log statement called depending on the logging level, in slf4j? For example, in the statement `log.debug("message with arg1: {}, arg2: {}", someobj.getArg1(), someobj.getArg2())`, are the getters called only if debug is enabled, or in any case? – Steven Solomon Aug 08 '15 at 00:21
  • @user664894 In that example, `getArg1()` and `getArg2()` are called in any case, without regard to the log level. If those accessors are very expensive to call, you could wrap them in some object that would perform the call lazily, only if the log level was high enough. – erickson Aug 10 '15 at 17:48
  • 1
    Has this changed in log4j 2? – SnakeDoc Jun 15 '16 at 15:40
  • 4
    @SnakeDoc No. It's fundamental to method calling: expressions in method arg-lists are effectively evaluated before the call. If those expressions are a) deemed expensive and b) only wanted under certain conditions (eg when debug is enabled), then your only choice is to put a condition check around the call, and the framework can't do that for you. The thing with formatter-based log methods is that you can pass some Object(s) (which is essentially free), and the logger will call `toString()` only if required. – SusanW Feb 02 '17 at 14:00
  • @SnakeDoc, see [comment](https://stackoverflow.com/a/59767872/6865356) below about Log4j since version `2.4` – Dan Brandt Jan 16 '20 at 10:44
  • @erickson Can I ask if the guard statement is still needed if i used parameterized logging as seen [here](https://stackoverflow.com/a/58699969/16034206) and [here](https://stackoverflow.com/a/50144350/16034206)? – experiment unit 1998X Apr 02 '23 at 04:38
  • 1
    @experimentunit1998X No the guard is not necessary when using parameterized logging. I cover this starting at “Better yet…” – erickson Apr 03 '23 at 01:36
  • @erickson thanks. i was under the impression that parameterized logging *could* build the string eagerly, but after checking the guide for the logging framework, i found out that it wasnt. – experiment unit 1998X Apr 03 '23 at 03:40
35

Since in option 1 the message string is a constant, there is absolutely no gain in wrapping the logging statement with a condition, on the contrary, if the log statement is debug enabled, you will be evaluating twice, once in the isDebugEnabled() method and once in debug() method. The cost of invoking isDebugEnabled() is in the order of 5 to 30 nanoseconds which should be negligible for most practical purposes. Thus, option 2 is not desirable because it pollutes your code and provides no other gain.

Ceki
  • 26,753
  • 7
  • 62
  • 71
24

Using the isDebugEnabled() is reserved for when you're building up log messages by concatenating Strings:

Var myVar = new MyVar();
log.debug("My var is " + myVar + ", value:" + myVar.someCall());

However, in your example there is no speed gain as you're just logging a String and not performing operations such as concatenation. Therefore you're just adding bloat to your code and making it harder to read.

I personally use the Java 1.5 format calls in the String class like this:

Var myVar = new MyVar();
log.debug(String.format("My var is '%s', value: '%s'", myVar, myVar.someCall()));

I doubt there's much optimisation but it's easier to read.

Do note though that most logging APIs offer formatting like this out of the box: slf4j for example provides the following:

logger.debug("My var is {}", myVar);

which is even easier to read.

Alex
  • 8,093
  • 6
  • 49
  • 79
  • 12
    Your usage of String.format(...), while making the log line easier to read, may in fact impact performance in a bad way. The SLF4J way of doing this sends the parameters into the logger.debug method and there the evaluation of isDebugEnabled is done _before_ the string is constructed. The way you're doing it, with String.format(...), the string will be constructed before the method call to logger.debug is done so you will have paid the penalty of the string building even if the debug level is not enabled. Sorry for the nit picking, just trying to avoid confusion for newbies ;-) – StFS Dec 09 '15 at 00:50
  • 3
    String.format is 40 times slower than concat & slf4j has limitation of 2 params See numbers here: https://stackoverflow.com/questions/925423/is-it-better-practice-to-use-string-format-over-string-concatenation-in-java#925444 I have seen many a profiler graph where the format operation is wasted in debug statements when the production system is running at log level of INFO or ERROR – AztecWarrior_25 Mar 10 '18 at 00:51
15

In Java 8, you don't have to use isDebugEnabled() to improve the performance.

https://logging.apache.org/log4j/2.0/manual/api.html#Java_8_lambda_support_for_lazy_logging

import java.util.logging.Logger;
...
Logger.getLogger("hello").info(() -> "Hello " + name);
Marcono1234
  • 5,856
  • 1
  • 25
  • 43
cybaek
  • 865
  • 1
  • 8
  • 10
10

As others have mentioned using the guard statement is only really useful if creating the string is a time consuming call. Specific examples of this are when creating the string will trigger some lazy loading.

It is worth noting that this problem can be completed avoided by using Simple Logging Facade for Java or (SLF4J) - http://www.slf4j.org/manual.html . This allows method calls such as:

logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);

This will only convert the passed in parameters to strings if debug is enabled. SLF4J as its name suggests is only a facade and the logging calls can be passed to log4j.

You could also very easily "roll your own" version of this.

Hope this helps.

Pablojim
  • 8,542
  • 8
  • 45
  • 69
8

Short Version: You might as well do the boolean isDebugEnabled() check.

Reasons:
1- If complicated logic / string concat. is added to your debug statement you will already have the check in place.
2- You don't have to selectively include the statement on "complex" debug statements. All statements are included that way.
3- Calling log.debug executes the following before logging:

if(repository.isDisabled(Level.DEBUG_INT))
return;

This is basically the same as calling log. or cat. isDebugEnabled().

HOWEVER! This is what the log4j developers think (as it is in their javadoc and you should probably go by it.)

This is the method

public
  boolean isDebugEnabled() {
     if(repository.isDisabled( Level.DEBUG_INT))
      return false;
    return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel());
  }

This is the javadoc for it

/**
*  Check whether this category is enabled for the <code>DEBUG</code>
*  Level.
*
*  <p> This function is intended to lessen the computational cost of
*  disabled log debug statements.
*
*  <p> For some <code>cat</code> Category object, when you write,
*  <pre>
*      cat.debug("This is entry number: " + i );
*  </pre>
*
*  <p>You incur the cost constructing the message, concatenatiion in
*  this case, regardless of whether the message is logged or not.
*
*  <p>If you are worried about speed, then you should write
*  <pre>
*    if(cat.isDebugEnabled()) {
*      cat.debug("This is entry number: " + i );
*    }
*  </pre>
*
*  <p>This way you will not incur the cost of parameter
*  construction if debugging is disabled for <code>cat</code>. On
*  the other hand, if the <code>cat</code> is debug enabled, you
*  will incur the cost of evaluating whether the category is debug
*  enabled twice. Once in <code>isDebugEnabled</code> and once in
*  the <code>debug</code>.  This is an insignificant overhead
*  since evaluating a category takes about 1%% of the time it
*  takes to actually log.
*
*  @return boolean - <code>true</code> if this category is debug
*  enabled, <code>false</code> otherwise.
*   */
Andrew Carr
  • 777
  • 6
  • 16
  • 2
    Thank you for including the JavaDoc. I knew that I'd seen this advice somewhere before and was trying to find a definitive reference. This is, if not definitive, at least very well informed. – Simon Peter Chappell Aug 11 '17 at 13:55
6

Option 2 is better.

Per se it does not improve performance. But it ensures performance does not degrade. Here's how.

Normally we expect logger.debug(someString);

But usually, as the application grows, changes many hands, esp novice developers, you could see

logger.debug(str1 + str2 + str3 + str4);

and the like.

Even if log level is set to ERROR or FATAL, the concatenation of strings do happen ! If the application contains lots of DEBUG level messages with string concatenations, then it certainly takes a performance hit especially with jdk 1.4 or below. (Iam not sure if later versions of jdk internall do any stringbuffer.append()).

Thats why Option 2 is safe. Even the string concatenations dont happen.

Sathya
  • 2,371
  • 5
  • 19
  • 27
4

Like @erickson it depends. If I recall, isDebugEnabled is already build in the debug() method of Log4j.
As long as you're not doing some expensive computations in your debug statements, like loop on objects, perform computations and concatenate strings, you're fine in my opinion.

StringBuilder buffer = new StringBuilder();
for(Object o : myHugeCollection){
  buffer.append(o.getName()).append(":");
  buffer.append(o.getResultFromExpensiveComputation()).append(",");
}
log.debug(buffer.toString());

would be better as

if (log.isDebugEnabled(){
  StringBuilder buffer = new StringBuilder();
  for(Object o : myHugeCollection){
    buffer.append(o.getName()).append(":");
    buffer.append(o.getResultFromExpensiveComputation()).append(",");
  }
  log.debug(buffer.toString());
}
Michael Myers
  • 188,989
  • 46
  • 291
  • 292
3

For a single line, I use a ternary inside of logging message, In this way I don't do the concatenation:

ej:

logger.debug(str1 + str2 + str3 + str4);

I do:

logger.debug(logger.isDebugEnable()?str1 + str2 + str3 + str4:null);

But for multiple lines of code

ej.

for(Message mess:list) {
    logger.debug("mess:" + mess.getText());
}

I do:

if(logger.isDebugEnable()) {
    for(Message mess:list) {
         logger.debug("mess:" + mess.getText());
    }
}
rbeltrand
  • 108
  • 3
3

Since many people are probably viewing this answer when searching for log4j2 and nearly all current answers do not consider log4j2 or recent changes in it, this should hopefully answer the question.

log4j2 supports Suppliers (currently their own implementation, but according to the documentation it is planned to use Java's Supplier interface in version 3.0). You can read a little bit more about this in the manual. This allows you to put expensive log message creation into a supplier which only creates the message if it is going to be logged:

LogManager.getLogger().debug(() -> createExpensiveLogMessage());
Marcono1234
  • 5,856
  • 1
  • 25
  • 43
2

It improves the speed because it's common to concatenate strings in the debug text which is expensive eg:

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text" + someState);
}
Tom
  • 43,583
  • 4
  • 41
  • 61
  • 1
    If we are using jdk 1.5 and onwards then I think so concatenate strings will not make any difference. – Silent Warrior Jun 08 '09 at 06:11
  • How come? What would JDK5 do differently? – javashlook Jun 08 '09 at 06:59
  • 1
    In jdk 1.5 if we concatenate strings in single statement then internally it uses StringBuffer.append() method only. So it doesn't affects on performance. – Silent Warrior Jun 08 '09 at 08:59
  • 2
    String concatenation undoubtedly takes time. However I'm not sure I would describe it as 'expensive'. How much time is saved in the above example ? Compared to what the surrounding code is actually doing ? (e.g. database reads or in-memory computation). I think these sort of statements need to be qualified – Brian Agnew Jun 08 '09 at 10:12
  • 1
    Even JDK 1.4 will not create new String objects with simple string concatenation. The performance penalty comes from using StringBuffer.append() when no string should be displayed at all. – javashlook Jun 10 '09 at 10:26
1

As of 2.x, Apache Log4j has this check built in, so having isDebugEnabled() isn't necessary anymore. Just do a debug() and the messages will be suppressed if not enabled.

ha9u63a7
  • 6,233
  • 16
  • 73
  • 108
0

I would recommend using Option 2 as de facto for most as it's not super expensive.

Case 1: log.debug("one string")

Case2: log.debug("one string" + "two string" + object.toString + object2.toString)

At the time either of these are called, the parameter string within log.debug (be it CASE 1 or Case2) HAS to be evaluated. That's what everyone means by 'expensive.' If you have a condition before it, 'isDebugEnabled()', these don't have to be evaluated which is where the performance is saved.

Jolly1234
  • 1,577
  • 12
  • 9
0

Since Log4j version 2.4 (orslf4j-api 2.0.0-alpha1) it's much better to use fluent API (or Java 8 lambda support for lazy logging), supporting Supplier<?> for log message argument, that can be given by lambda:

log.debug("Debug message with expensive data : {}", 
           () -> doExpensiveCalculation());

OR with slf4j API:

log.atDebug()
            .addArgument(() -> doExpensiveCalculation())
            .log("Debug message with expensive data : {}");
Dan Brandt
  • 605
  • 1
  • 7
  • 21
  • 2
    "Much better" is in the eye of the beholder :-) The latter is harder to understand and longer than what you are trying to avoid. – oligofren Oct 06 '20 at 14:16
0

If you use option 2 you are doing a Boolean check which is fast. In option one you are doing a method call (pushing stuff on the stack) and then doing a Boolean check which is still fast. The problem I see is consistency. If some of your debug and info statements are wrapped and some are not it is not a consistent code style. Plus someone later on could change the debug statement to include concatenate strings, which is still pretty fast. I found that when we wrapped out debug and info statement in a large application and profiled it we saved a couple of percentage points in performance. Not much, but enough to make it worth the work. I now have a couple of macros setup in IntelliJ to automatically generate wrapped debug and info statements for me.

Javamann
  • 2,882
  • 2
  • 25
  • 22
-1

Log4j2 lets you format parameters into a message template, similar to String.format(), thus doing away with the need to do isDebugEnabled().

Logger log = LogManager.getFormatterLogger(getClass());
log.debug("Some message [myField=%s]", myField);

Sample simple log4j2.properties:

filter.threshold.type = ThresholdFilter
filter.threshold.level = debug
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d %-5p: %c - %m%n
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = debug
rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT
Andre
  • 390
  • 3
  • 8