27

Recently I encountered a situation where Application Loglevel changes dynamically. Application Admin can set it to INFO/DEBUG/ WARN from front end. Based on the log level choosen be him application logging must be changed.

I am sure loggers support this scenario. How can I achieve this?

Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277
Narendra
  • 5,635
  • 10
  • 42
  • 54
  • 4
    You can't do this using the SLF4J API; you have to configure the logging backend (log4j, in your case) using its own API. – Martin Ellis Nov 18 '12 at 18:10
  • Do you want to change the level at which some messages are sent into the logging framework, or the level which is used to filter messages written to the log? – Tom Anderson Nov 18 '12 at 18:48
  • Can you tell me the difference between both? – Narendra Nov 19 '12 at 05:25
  • Possible duplicate of [Setting log level of message at runtime in slf4j](http://stackoverflow.com/questions/2621701/setting-log-level-of-message-at-runtime-in-slf4j) – Martin Schröder Apr 19 '17 at 09:15
  • 2
    There are actually three ways of changing the logging level. (1) changing the level of all logging that is output by an appender, (2) changing the level of logging output for a class-specific logger, and (3) changing the level of individual logging statements at places in the code. With `SLF4j` you can't do #3, but you could in log4j if you use `logger.log("stuff", LEVEL);` since `LEVEL` could be from a variable. The latter would be something only required in very specific situations. So it's most likely Narendra meant changing the logging level per appender. – Adam Dec 21 '17 at 10:04

6 Answers6

19

It is not possible to change the log level dynamically in slf4j, but some backends for slf4j support it, including log4j.

This solution worked for me:

org.apache.log4j.Logger logger4j = org.apache.log4j.Logger.getRootLogger();
logger4j.setLevel(org.apache.log4j.Level.toLevel("ERROR"));

(Source: http://prateep.info/2015/12/12/Dynamically-change-log-level-in-SLF4j-Log4J-with-Standalone-Java-Class/)

The disadvantage of this solution is that it uses the backend directly, which you're not supposed to do when using slf4j because the point of slf4j is to provide an abstraction away from the specific backend you're using.

outofthecave
  • 477
  • 6
  • 15
10

Consider Logback http://logback.qos.ch/ - "a successor to the popular log4j project, picking up where log4j leaves off". If instructed to do so, logback-classic will scan for changes in its configuration file and automatically reconfigure itself when the configuration file changes. Besides, you can control Logback's logging levels with JMX.

Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
7

For SLF4J, this code will demonstrate how to control logger level programmatically (at run-time).

This answer assumes you are using Logback Classic:

Assuming default SLF4J configuration:

final Logger logger = LoggerFactory.getLogger(LoggerListener.class);
// These five statements will log.
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug");
logger.trace("trace");

final ch.qos.logback.classic.Logger logger2 = (ch.qos.logback.classic.Logger) logger2;
@Nullable
final Level nullablePrevLevel = logger2.getLevel();
final Level level = Level.INFO;
logger2.setLevel(level);
logger.info("Change log level: [{}]->[{}]", nullablePrevLevel, level);

// These three statements will log.
logger.error("error");
logger.warn("warn");
logger.info("info");
// These two statements will not log.
logger.debug("debug");
logger.trace("trace");
kevinarpe
  • 20,319
  • 26
  • 127
  • 154
6

I had to do this once with log4j. The only way I could figure out how to do it was to call getAllAppenders on the Logger object. Then, loop through the appenders. If they extend the AppenderSkeleton class (they should), they will have the setThreshold method. Call this method with your new Level as the parameter. Subsequent calls to the logger should use the new level. This will set the level in memory, but not in your log4j configuration file. You may want to do this, too, unless it gets changed automatically when the admin changes the level via the front end. If it's an option, you may want to consider following Evgeniy Dorofeev's advice and use logback. It sounds like it would be easier.

David A
  • 490
  • 4
  • 10
2

As of slf4j version 1.7.26, I was able to change the logging level.

Here is the logback.xml in the source folder. In case of spring boot app, you might want to place it in the resources folder.

<configuration scan="true" scanPeriod="20000">
    <include file="C:/logback-ext.xml"/>
</configuration>

The logback-ext.xml file is kept at any external location. The scanPeriod is in milliseconds. In case this fails, try using include resource instead of include file in logback.xml.

<included>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>
          %d{yyyy-MM-dd HH:mm:ss} %-5p [%t] --- %c{1}.%M:%L :: %m %n
      </pattern>
    </encoder>
  </appender>
  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>
</included>

I was able to change the logging level, logging pattern, attach/ detach new appenders and add/ remove appenders.

These are the dependencies in pom.xml

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
<scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

Cheers!

ScottSummers
  • 310
  • 1
  • 13
0

We're building dynamic log adjustment like this as a service and yes, slf4j itself doesn't support this. We solved this by doing an implementation in each popular logging framework and then publishing a different maven artifact.

Rather than set the level on the root logger, you may get better control / precision by using the filtering APIs. eg

public abstract class BaseTurboFilter extends TurboFilter {

  @Override
  public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) {
    if (YOUR_LOGIC) {
      return FilterReply.ACCEPT;
    } 
    return FilterReply.NEUTRAL;
  }
}

Full example in a Logback turbo filter

Full example in a Log4j2 AbstractLoggingListener

jdwyah
  • 1,253
  • 1
  • 11
  • 22