Breaking SLF4J decoupling

The main reason to use SLF4J as façade to the logging framework that we actually use is to decouple our Java application to the concrete logging system. That means that usually we won't make from our application any assumption on which actual logger framework is in the background.

But sometimes we need a more pragmatic approach. Besides, if we are using Logback, maybe we are not even interested in portability, and we are using SLF4J as interface because Logback is built to work in this way.

The need of changing the log level programmatically is one of those tasks that requires to delve in the actual logging framework details. It is not a common task, we usually prefer to use a configuration file, but maybe we can't afford to stop and start our application, so we have to be more creative than usual.
The SLF4J Logger does not have any visibility on it, we have to go down to the real logger object.

Firstly, let's write an utility method to check the current log level:
void checkLogging() {
    if(log.isTraceEnabled())
        System.out.println("Log trace enabled");
    else if(log.isDebugEnabled())
        System.out.println("Log debug enabled");
    else if(log.isInfoEnabled())
        System.out.println("Log info enabled");
    else if(log.isWarnEnabled())
        System.out.println("Log warn enabled");
    else if(log.isErrorEnabled())
        System.out.println("Log error enabled");
    else
        System.out.println("Log disabled");
}
The member variable log is defined in this way:
private static final Logger log = LoggerFactory.getLogger("test.TestLogback");
If this does not look clear to you, you could have a look to my previous post on how to set up a Java application to work with SLF4J and Logback.

We could change the logger level only for one specific logger, but here we changes the root logger level, to show, as a bonus, how the log level on an ancestor impacts on the current one when, as in this case, no specific log level is specified in the configuration file for it:
checkLogging(); // 1

org.slf4j.Logger sl = LoggerFactory.getLogger("root"); // 2
if(sl instanceof ch.qos.logback.classic.Logger) { // 3
    ch.qos.logback.classic.Logger cl = (ch.qos.logback.classic.Logger)sl; // 4

    cl.setLevel(Level.ALL); // 5
    checkLogging(); // 6

    cl.setLevel(Level.ERROR); // 7
    checkLogging();

    cl.setLevel(Level.OFF); // 8
    checkLogging();
}
1. The default log level for a Logback logger is debug.
2. The object returned is an SLF4J Logger, I defined it showing its class full name to stress this fact.
3. It comes handy here that Logback implements the SLF4J interfaces. To ensure that the concrete logging framework is Logback, we can just check the actual type of the logger retrieved from the logger factory.
4. We safely downcast the logger to the Logback specific type.
5. Enabling full logging for the root logger.
6. Root is an ancestor of any logger, our current logger, and no intermediate one, has explicitly set its log level, so the change of the root log level reflects directly on it.
7. Another change in root (and descendants) log level.
8. Log is disabled.

If you assemble this code in a your application you should get the same output I got:
Log debug enabled
Log trace enabled
Log error enabled
Log disabled

2 comments:

  1. The only problem is that if you don't have the logback jar you get an error like:
    not found: value ch

    ReplyDelete
    Replies
    1. Right. Actually, if you are using an IDE, it should complain even before that it doesn't know what that class is. Thank you to point it out.

      Delete