Reputation: 22850
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.
Upvotes: 179
Views: 202905
Reputation: 1292
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;
}
}
Upvotes: 2
Reputation: 381
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
Upvotes: 5
Reputation: 1149
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);
}
}
Upvotes: 0
Reputation: 274878
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>
Upvotes: 297
Reputation: 67
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;
}
}
Upvotes: -2
Reputation: 28939
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);
Upvotes: -1
Reputation: 2234
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");
Upvotes: 6
Reputation: 1482
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]
Upvotes: 13
Reputation: 4289
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"));
}
}
Upvotes: 5
Reputation: 52665
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?
Upvotes: 10