Reputation: 27822
I am developing a Java-based command-line tool. I want to leverage slf4j's logging abstraction.
When the application is regularly invoked
java -jar myapp.jar someparameter
then the application shall give "plain" CLI output on stdout
such as:
/some/file1
/some/file2
When instead the application is invoked with an additional --verbose
flag / option
java -jar myapp.jar --verbose someparameter
then the application shall give more advanced logging:
16:06:09.031 [main] DEBUG com.example.MyApp - Starting application.
16:06:09.031 [main] DEBUG com.example.MyApp - Entering directory /some/.
16:06:09.046 [main] INFO com.example.MyApp - /some/file1
16:06:09.046 [main] INFO com.example.MyApp - /some/file2
While it is is easy to determine whether --verbose
was provided (e.g. by using the jCommander CLI library), slf4j does not seem to allow setting the root logger level during runtime, nor does slf4j allow to change the log entry pattern layout during runtime.
Upvotes: 2
Views: 20519
Reputation: 27822
I came up with the following solution:
For the executable Java application side of the command-line tool, I decide to use slf4j and logback.
I bundle the application with two logback configuration files, logback.xml
and logback-verbose.xml
.
logback.xml
:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
<logger name="com.example.some.chatty.library.i.want.to.turn.off.logging" level="OFF">
<appender-ref ref="STDOUT" />
</logger>
</configuration>
logback-verbose.xml
:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{ISO8601} [%-17thread] %-5level %-36logger - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Early in the application start-up phase, I check for the --verbose
flag. If that flag is set, I command logback
to use configuration file logback-verbose.xml
instead of the default logback.xml
:
Application.java
:
package com.example.my;
//
public class Application {
public static void main(String[] args) throws Exception {
// reconfigure JUL for slf4j
LogManager.getLogManager().reset();
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
Logger.getLogger("global").setLevel(Level.FINEST);
// about to check for verbose output request
if ( isVerboseOutputRequested() ) {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
// Call context.reset() to clear any previous configuration, e.g. default
// configuration. For multi-step configuration, omit calling context.reset().
context.reset();
configurator.doConfigure( getClass().getResourceAsStream("/logback-verbose.xml") );
}
catch (JoranException je) {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
}
startApplication();
}
// ... implement isVerboseOutputRequested() and startApplication()
}
This setup keeps in mind a separation of an executable Java application apart from a library used by that application.
The library can depend on slf4j only, while the executable Java application (being just a thin wrapper around that library) depends on both slf4j and logback in order to provide an actual logging implementation for the final executable artifact.
Thanks to Carlitos Way and Tansir1 for providing answers that lead to this solution.
Upvotes: 4
Reputation: 3424
I suggest to use a "decorator" pattern for this problem.
You will have to define your own LoggerFactory, and define the method "getLogger"... something like this:
public class LoggerFactory {
public static Logger getLogger(String category) {
return (Boolean.parseBoolean(System.getProperty("verboseFlag"))
? org.slf4j.LoggerFactory.getLogger("verbose-" + category)
: org.slf4j.LoggerFactory.getLogger(category);
}
Then in your configuration file, assuming that your are using log4j, you must configure two category; one with the verbose prefix another without it... later, each category must define its own appenders and each appender must be configured accordingly...
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="VerboseConsoleAppender" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] %m%n"/>
</layout>
</appender>
<appender name="RegularConsoleAppender" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n"/>
</layout>
</appender>
<category name="verbose-org.xyz" additivity="false">
<priority value="DEBUG" />
<appender-ref ref="VerboseConsoleAppender" />
</category>
<category name="org.xyz" additivity="false">
<priority value="INFO" />
<appender-ref ref="RegularConsoleAppender" />
</category>
</log4j:configuration>
Upvotes: 1
Reputation: 1819
Changing the logging configuration dynamically at runtime is not part of SLF4J. Logging configuration is specific to the abstracted logging mechanism (log4j, logback, etc).
Assuming you're using the Logback library underneath SLF4J you can reconfigure everything programatically by changing the JoranConfigurator. This code snippet was copied from Invoking JoranConfigurator directly in the Logback documentation website. You can change the logging pattern, appenders, logging level, and just about every other parameter about the loggers from the JoranConfigurator.
package chapters.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;
public class MyApp3 {
final static Logger logger = LoggerFactory.getLogger(MyApp3.class);
public static void main(String[] args) {
// assume SLF4J is bound to logback in the current environment
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
// Call context.reset() to clear any previous configuration, e.g. default
// configuration. For multi-step configuration, omit calling context.reset().
context.reset();
configurator.doConfigure(args[0]);
} catch (JoranException je) {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
logger.info("Entering application.");
Foo foo = new Foo();
foo.doIt();
logger.info("Exiting application.");
}
}
Upvotes: 0