Set log4j2-Log Level via args4j parameters

Screenshot of a terminal executing the program

Implementing good logging in an application can be challenging. Using args4j we can easily set the root logging level of log4j via a command line parameter.

Introduction

When it comes to logging in Java, log4j2 is a wide-used framework. For small applications, using the configuration files to change the log level might be a bit of an overkill and a command line parameter is much more appropriate. If you don’t want to parse the command line parameters yourself, you can use a framework for that. args4j uses annotations to automatically populate the fields or properties of an object with the appropriate command line parameters.

In my recent project, I had the challenge of combining those two.

Simple Scenario

Let’s say we have a very simple class which creates different kinds of log messages:

class Foo {
    private static Logger logger = LogManager.getLogger();

    public void bar(int count) {
        for (int i = 0; i < count; i++) {
            switch (i % 6) {
            case 0:
                logger.trace("Trace message");
                break;
            case 1:
                logger.debug("Debug message");
                break;
            case 2:
                logger.info("Info message");
                break;
            case 3:
                logger.warn("Warn message");
                break;
            case 4:
                logger.error("Error message");
                break;
            case 5:
                logger.fatal("Fatal message");
                break;
            }
        }
    }
}

We call this class by the following main method which takes care of calling args4j to parse the command line arguments:

public static void main(String[] args) {
    Parameters parameters = new Parameters();
    CmdLineParser parser = new CmdLineParser(parameters);
    try {
        parser.parseArgument(args);
    } catch (Exception ex) {
        parser.printUsage(System.out);
        return;
    }       

    int count = parameters.getCount();
    new Foo().bar(count);
}

Parameters.java:

public class Parameters {
    private int count = 10;

    public Parameters() {
    }

    @Option(name="-count", usage="The number of messages to create (must be >= 0)")
    public void setCount(int value) {
        if (value <= 0)
            throw new IllegalArgumentException("value must be >= 0");

        this.count = value;
    }
    public int getCount() {
        return this.count;
    }
}

Nothing fancy so far – we can now call the application from the command line and pass count parameters:

java -jar logtest.jar -count 10

ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.
23:58:28.892 [main] ERROR args4jlog4j.Foo - Error message
23:58:28.893 [main] FATAL args4jlog4j.Foo - Fatal message

Supressing the message that no configuration has been found

By default, log4j uses level ERROR and logs to the console. In this case, this is almost exactly what we need. However, as seen in the output, log4j creates a message that no configuration file was found and thus only errors will be logged to the console. To get rid of this message, we need to create a configuration file. I decided to go for a XML file – again, very simple (name the file log4j2.xml and put it somewhere in the classpath):

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %msg%n" />
    </Console>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="Console"/>
    </Root>
  </Loggers>
</Configuration>

Now, the message is gone and our logging still works. Note that you can set the default logging level of the root logger in the XML file (level attribute in the Root node). We can also customize the pattern. Result:

java -jar logtest.jar -count 10

00:05:47.799 [main] ERROR Error message
00:05:47.801 [main] FATAL Fatal message

Setting the log level via command line parameter

In order to realize our goal to make the log level customizable via a parameter we only need to modify our Parameters class slightly:

  1. We introduce an enum LogLevel containing the log levels we want to offer for selection. Note: We can’t use log4j’s org.apache.logging.log4j.Level (in theory, we probably could by writing a custom args4j handler, but the custom enum seems like the easier solution)
  2. We create a setter method accepting an object of this enum as a parameter which converts it to a log4j.Level and calls Configurator.setRootLevel(level);
  3. What’s left is that we need to decorate the setter method with an @Option annotation.

The result looks like this:

enum LogLevel {
    TRACE,
    DEBUG,
    INFO,
    WARN,
    ERROR,
    FATAL,
}

@Option(name="-logLevel", usage="The log level")
public void setLogLevel(LogLevel value) {
    String levelName = value.toString();
    Level level = Level.getLevel(levelName);
    Configurator.setRootLevel(level);
}

Testing the result

And we’re done: We can now pass the log level as a command line option:

java -jar logtest.jar -count 10 -logLevel DEBUG

00:15:57.717 [main] DEBUG Debug message
00:15:57.718 [main] INFO  Info message
00:15:57.718 [main] WARN  Warn message
00:15:57.718 [main] ERROR Error message
00:15:57.719 [main] FATAL Fatal message
00:15:57.719 [main] DEBUG Debug message
00:15:57.719 [main] INFO  Info message
00:15:57.719 [main] WARN  Warn message

Bonus: If we pass an invalid logLevel, args4j will take care of that, too:

java -jar logtest.jar -count 10 -logLevel NOPE

 -count N                               : The number of messages to create
                                          (must be >= 0)
 -logLevel [TRACE | DEBUG | INFO |      : The log level
 WARN | ERROR | FATAL]

Code

Code is available at Github.