How to change root logging level programmatically for logback
Asked Answered
I

10

172

I have the following logback.xml file:

<configuration debug="true"> 

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
<encoder>
  <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="debug">
  <appender-ref ref="STDOUT" />
</root>
</configuration>

Now, upon the occurrence of a specific event, I want to programmatically change the level of the root logger from debug to error. I can't use variable substitution, it is mandatory that I do this within the code.

How can it be done ? Thanks.

Ingleside answered 1/10, 2010 at 9:8 Comment(0)
H
289

Try this:

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;

Logger root = (Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
root.setLevel(Level.INFO);

Note that you can also tell logback to periodically scan your config file like this:

<configuration scan="true" scanPeriod="30 seconds" > 
  ...
</configuration> 
Herndon answered 1/10, 2010 at 9:52 Comment(11)
It should be noted that the purpose of slf4j is to abstract away the logging framework, but that first method does away with that by referencing the logging framework directly.Dramatization
If you do this and get a ClassCastException, it's most likely due to having multiple SLF4J bindings on the classpath. The log output will indicate this and which bindings are present to let you determine which one(s) you need to exclude.Overflight
In response to Tim's comment, it should also be noted that sometimes you're just doing development and the logger is spewing out things you're not interested in and you just temporarily want to turn it off, so in such a case that's not something to worry about.Brasher
Slf4j provides an API so that libraries can log application logs using whatever log framework the application developer wants. The point is that the application developer still must choose a log framework, depend on it, and configure it. Configuring the logger as dogbane has done does not violate this principle.Tightlipped
It does violate the principle. Otherwise, why use slf4j at all? Why not call the logback methods to also do logging?Coze
@JohnWiseman If you want it be configured, then you have to configure it somewhere. As slf4j offers nothing in this respect, there will always be something dependent on the underlying logger. Be it a piece of code or a configuration file. +++ If it should be done programmatically as the OP requested, then you have no choice. Still, advantages remain: 1. Only a tiny part of the code depends on the concrete logger engine (and it could be written so that it may handle different implementations). 2. You can configure libraries written using other loggers, too.Postlude
@JohnWiseman Logback is designed from the ground up to be an SLF4J implementation; unlike say log4j, there is no "logback API" which is completely separate from SLF4J. The only reason to make calls to "logback methods" would be for things like this, which aren't covered by the SLF4J API.Mystical
@TimGautier, you're point about abstracting the logging framework is important. I want to add that it is not always good to abstract the logging framework. There are times when calling the framework directly is beneficial and good. If you are writing a library then abstraction is good. If you are writing an application and you're in charge of the logging and choice of logging framework then taking full advantage of the logging framework could be a good idea. I wouldn't in such a case default to abstraction without a good reason. There are tradeoffs and not one good answer.Apollinaire
This is not a solution for SLF4J.Stroll
Why does it have to be so complicated for something like Logging, shouldn't there be a direct way to change logging level in the code itself. How does following the principle of particular library take precedence over its simplicity? Coming from a Python world, I fail to understand why something as simple as Logging is so complicated in Java/Scala.Unglue
@AbhinandanDubey There is. You have just chosen to use an abstraction layer. If that is not a choice you understood or you do not want that, just use the library directly. That's exactly as in Python.Smallman
L
13

using logback 1.1.3 I had to do the following (Scala code):

import ch.qos.logback.classic.Logger
import org.slf4j.LoggerFactory    
...
val root: Logger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).asInstanceOf[Logger]
Loudmouthed answered 22/12, 2015 at 16:34 Comment(1)
it helped me, I was looking for the same to do in scala and logback combination. thanks man :)Leventis
L
11

I assume you are using logback (from the configuration file).

From logback manual, I see

Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

Perhaps this can help you change the value?

Longdistance answered 1/10, 2010 at 9:46 Comment(1)
And then wat? rootLogger will be instance of org.slf4j.Logger which doesn't have setLevel method.Chit
C
6

I think you can use MDC to change logging level programmatically. The code below is an example to change logging level on current thread. This approach does not create dependency to logback implementation (SLF4J API contains MDC).

<configuration>
  <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter">
    <Key>LOG_LEVEL</Key>
    <DefaultThreshold>DEBUG</DefaultThreshold>
    <MDCValueLevelPair>
      <value>TRACE</value>
      <level>TRACE</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>DEBUG</value>
      <level>DEBUG</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>INFO</value>
      <level>INFO</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>WARN</value>
      <level>WARN</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>ERROR</value>
      <level>ERROR</level>
    </MDCValueLevelPair>
  </turboFilter>
  ......
</configuration>
MDC.put("LOG_LEVEL", "INFO");
Cashew answered 12/10, 2017 at 15:40 Comment(0)
R
5

As pointed out by others, you simply create mockAppender and then create a LoggingEvent instance which essentially listens to the logging event registered/happens inside mockAppender.

Here is how it looks like in test:

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.Appender;

@RunWith(MockitoJUnitRunner.class)
public class TestLogEvent {

// your Logger
private Logger log = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);

// here we mock the appender
@Mock
private Appender<ILoggingEvent> mockAppender;

// Captor is generic-ised with ch.qos.logback.classic.spi.LoggingEvent
@Captor
private ArgumentCaptor<LoggingEvent> captorLoggingEvent;

/**
 * set up the test, runs before each test
 */
@Before
public void setUp() {
    log.addAppender(mockAppender);
}

/**
 * Always have this teardown otherwise we can stuff up our expectations. 
 * Besides, it's good coding practise
 */
@After
public void teardown() {
    log.detachAppender(mockAppender);
}


// Assuming this is your method
public void yourMethod() {
    log.info("hello world");
}

@Test
public void testYourLoggingEvent() {

    //invoke your method
    yourMethod();

    // now verify our logging interaction
    // essentially appending the event to mockAppender
    verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture());

    // Having a generic captor means we don't need to cast
    final LoggingEvent loggingEvent = captorLoggingEvent.getValue();

    // verify that info log level is called
    assertThat(loggingEvent.getLevel(), is(Level.INFO));

    // Check the message being logged is correct
    assertThat(loggingEvent.getFormattedMessage(), containsString("hello world"));
}
}
Roybn answered 21/8, 2015 at 10:13 Comment(0)
P
4

So I mostly agree with the top answer but found it to be slightly different in 2023. I found that the following works

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;

final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
final Logger logger = loggerContext.exists(org.slf4j.Logger.ROOT_LOGGER_NAME); // give it your logger name
final Level newLevel = Level.toLevel("ERROR", null); // give it your log level
logger.setLevel(newLevel);

The primary difference of note is instead of getLogger I had to use getILoggerFactory. To see additional related posts to this see Programmatically change log level in Log4j2 or if you want to be able to this per request see Change priority level in log4j per request

Porous answered 11/2, 2023 at 21:3 Comment(0)
B
2

Another approach is to use a Logback TurboFilter. This can give us more control.

Changing the level of the logger itself only let's us turn a particular logger on or off. What if we want to get DEBUG for user:123 or team:456 for everything in the com.example.billing module for the next 90 minutes but stay are WARN for everything else?

If we use a TurboFilter, we have access to the MDC where we can get the user context. And we can access a dynamic config system to get the rules for which users to match.

This is what https://github.com/prefab-cloud/prefab-cloud-java does using prefab.cloud as the dynamic config and UI.

Simplified:

public class PrefabMDCTurboFilter extends TurboFilter {

  private final ConfigClient configClient;

  PrefabMDCTurboFilter(ConfigClient configClient) {
    this.configClient = configClient;
  }

  public static void install(ConfigClient configClient) {
    PrefabMDCTurboFilter filter = new PrefabMDCTurboFilter(configClient);
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    loggerContext.addTurboFilter(filter);
  }

  @Override
  public FilterReply decide(
    Marker marker,
    Logger logger,
    Level level,
    String s,
    Object[] objects,
    Throwable throwable
  ) {
      Optional<Prefab.LogLevel> loglevelMaybe = configClient.getLogLevelFromStringMap(
        logger.getName(),
        MDC.getCopyOfContextMap()
      );
      if (loglevelMaybe.isPresent()) {
        Level calculatedMinLogLevelToAccept = LogbackLevelMapper.LEVEL_MAP.get(
          loglevelMaybe.get()
        );
        if (level.isGreaterOrEqual(calculatedMinLogLevelToAccept)) {
          return FilterReply.ACCEPT;
        }
        return FilterReply.DENY;
      }
      return FilterReply.NEUTRAL;
  }
}
Broderick answered 10/4, 2023 at 20:15 Comment(1)
Put a bit more effort into a description of this approach: prefab.cloud/blog/dynamically-changing-java-log-levelBroderick
K
0

You may intercept logging configuration via the LogManager.getLogManager().updateConfiguration() method. Just check for configuration properties which contains .level suffix, and replace default value with Level.ALL value.

public class Application {

    // Static initializer is executed when class is loaded by a class loader
    static {
        try {
            java.util.logging.LogManager.getLogManager().updateConfiguration(propertyName -> {
                // Check for the log level property
                if (propertyName.contains(".level")) {
                    // Level = ALL => logs all messages
                    return (oldValue, newValue) -> java.util.logging.Level.ALL.getName();
                } else {
                    // Identity mapper for other propeties
                    return (oldValue, newValue) -> newValue;
                }
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
Kratz answered 29/1, 2023 at 12:35 Comment(0)
M
-1

I seem to be having success doing

org.jboss.logmanager.Logger logger = org.jboss.logmanager.Logger.getLogger("");
logger.setLevel(java.util.logging.Level.ALL);

Then to get detailed logging from netty, the following has done it

org.slf4j.impl.SimpleLogger.setLevel(org.slf4j.impl.SimpleLogger.TRACE);
Michaelemichaelina answered 4/5, 2018 at 10:43 Comment(1)
org.slf4j.impl.SimpleLogger does not have a setLevel() in current v1.7.30.Mirabelle
S
-2

Here's a controller

@RestController
@RequestMapping("/loggers")
public class LoggerConfigController {

private final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(PetController.class);

@GetMapping()
public List<LoggerDto> getAllLoggers() throws CoreException {
    
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    
    List<Logger> loggers = loggerContext.getLoggerList();
    
    List<LoggerDto> loggerDtos = new ArrayList<>();
    
    for (Logger logger : loggers) {
        
        if (Objects.isNull(logger.getLevel())) {
            continue;
        }
        
        LoggerDto dto = new LoggerDto(logger.getName(), logger.getLevel().levelStr);
        loggerDtos.add(dto);
    }
    
    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("All loggers retrieved. Total of {} loggers found", loggerDtos.size());
    }
    
    return loggerDtos;
}

@PutMapping
public boolean updateLoggerLevel(
        @RequestParam String name, 
        @RequestParam String level
)throws CoreException {
    
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    
    Logger logger = loggerContext.getLogger(name);
    
    if (Objects.nonNull(logger) && StringUtils.isNotBlank(level)) {
        
        switch (level) {
            case "INFO":
                logger.setLevel(Level.INFO);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
                break;
                
            case "DEBUG":
                logger.setLevel(Level.DEBUG);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
                break;
                
            case "ALL":
                logger.setLevel(Level.ALL);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
                break;
                
            case "OFF":
            default: 
                logger.setLevel(Level.OFF);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
        }
    }
    
    return true;
}

}

Schertz answered 28/8, 2020 at 18:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.