180

I have the following the 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.

10 Answers 10

297

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> 
Sign up to request clarification or add additional context in comments.

11 Comments

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.
|
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] 

1 Comment

it helped me, I was looking for the same to do in scala and logback combination. thanks man :)
10

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?

1 Comment

And then wat? rootLogger will be instance of org.slf4j.Logger which doesn't have setLevel method.
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"); 

Comments

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

Comments

5

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

Comments

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

1 Comment

Put a bit more effort into a description of this approach: prefab.cloud/blog/dynamically-changing-java-log-level
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); } } 

Comments

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

1 Comment

org.slf4j.impl.SimpleLogger does not have a setLevel() in current v1.7.30.
-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; } 

}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.