ChatGPT解决这个技术问题 Extra ChatGPT

How to change root logging level programmatically for logback

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.


G
GreenGiant

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> 

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.
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.
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.
@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.
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.
R
Raghuram

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?


T
Todor Kolev

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]

S
Simple-Solution

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"));
}
}

S
SATO Yusuke

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");

u
user7610

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);

org.slf4j.impl.SimpleLogger does not have a setLevel() in current v1.7.30.
a
alevilla86

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;
}

}


关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now