Search code examples
javaloggingcode-coveragelog4j2apache-commons-logging

Enhanced code coverage with Commons logging and Log4j 2.0


I'm currently migrating from Log4j 1.2 to Log4j 2. We use Apache Commons Logging 1.1 (JCL), with Log4j2 as implementation.

Now when executing unit tests, statements like

 if (log.isInfoEnabled()) {
   log.info("example");
 }

will show up as uncovered lines in the coverage report if log log level is too high (e.g. WARN in this examole), as the if body wont be executed.

In Log4j 1 someone of my company thus wrote a custom logger that would return true for all log.isXXXEnabled() methods if it detects that it is run from Maven Surefire, like this:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.Log4JLogger;

public class Log4JEnhancedCoverage implements Log {

  private static final long serialVersionUID = -8715529047111858959L;

  private final Log logger;

  private final boolean mavenRun;

  public LogEnhancedCoverage(String name) {
    this.logger = new Log4JLogger(_name);
    this.mavenRun = TestsRunContext.isMavenSurefireRun();
  }

  @Override
  public boolean isTraceEnabled() {
    return (mavenRun) ? true : logger.isTraceEnabled();
  }

  @Override
  public void trace(Object message) {
   logger.trace(message);
  }

  // repeat for warn, info, etc
}

The effect is that each isXXXEnabled() block is executed, the log statement is redirected to log4j via a configuration file. Log4j itself will see that only messages of the specified log levels are shown, while code blocks for all levels are executed. Old: The problem is as you can see that a Log4j logger implementation is instanciated directly from the impl package of JCL. (Yes, they ship their own adapter implementation!).

In my new setup, I'm using the log4j-jcl artifact and I do not know how to properly create a Log4j 2 compatible logger in the constructor.

Update: log4j-jcl ships with its own log factory which loads a log4j 2 specific logger implementation. Thus the configuration of commons-logging for a specific logger is not honored at all.


Solution

  • Simply get the logger using the commons logging API rather than directly instantiating it and you'll be fine.

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    //REMOVE THIS: import org.apache.commons.logging.impl.Log4JLogger;
    
    public class Log4JEnhancedCoverage implements Log {
    
        private static final long serialVersionUID = -8715529047111858959L;
    
        private final Log logger;
    
        private final boolean mavenRun;
    
        public Log4JEnhancedCoverage(String name) {
            //REMOVE THIS: this.logger = new Log4JLogger(name);
            this.logger = LogFactory.getLog(name);
            ...