Programmatically change log level in Log4j2
Asked Answered
D

9

157

I'm interested in programmatically changing the log level in Log4j2. I tried looking at their configuration documentation but that didn't seem to have anything. I also tried looking in the package: org.apache.logging.log4j.core.config, but nothing in there looked helpful either.

Diphenyl answered 2/5, 2014 at 18:1 Comment(1)
If you dont get an answer here try the mail list, its generally looked in to once in 2 days by the main authors. Then come back and answer your own question :-)Purebred
M
229

The Easy Way :

EDITED according to log4j2 version 2.4 FAQ

You can set a logger’s level with the class Configurator from Log4j Core. BUT be aware that the Configurator class is not part of the public API.

// org.apache.logging.log4j.core.config.Configurator;
Configurator.setLevel("com.example.Foo", Level.DEBUG);

// You can also set the root logger:
Configurator.setRootLevel(Level.DEBUG);

Source

The Preferable Way :

EDITED to reflect changes in the API introduced in Log4j2 version 2.0.2

If you wish to change the root logger level, do something like this :

LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME); 
loggerConfig.setLevel(level);
ctx.updateLoggers();  // This causes all Loggers to refetch information from their LoggerConfig.

Here is the javadoc for LoggerConfig.

Maddock answered 2/5, 2014 at 18:24 Comment(12)
Right and if you want to change for just a particular logger (of a class/ package) get the context of that logger, setLevel and updateLoggers.Purebred
Such a convoluted way just to set the logging level. I'm sure there's a reason for doing it with five lines of code rather than the original one line in previous versions of log4j, but I just don't see it. In any case, thanks for this, @slaadvak!Silver
.updateLoggers() does not appear to be necessary. It seems that the changes done with .setLevel() are applied immediately.Andros
above statement is half true/false, updateLoggers is needed when you already logged something using the logger you are changing, if you never logged anything updateLogger isn't needed.Coaster
The call to updateLoggers is always required, even if you add a new LoggerConfig. UpdateLoggers causes all the loggers to reaasociate themselves with LoggerConfigs and to change their log level to that of their associated LoggerConfig. If you add a new LoggerConfig any Loggers that match the new LoggerConfig pattern will be redirected to it. The "convoluted" way is required because Loggers and their configuration have been separated in Log4j2.Paillette
Thanks! I am working on some standalone code before it gets integrated into our main codebase, and I just want all the damned logging to go to the console. Should be a common enough use case. I think needing 5 lines of code to do it is a bit excessive, but at least it works.Amish
Here is an answer that provides an updated, 1- or 2-line solution for newer versions of Log4J: https://mcmap.net/q/144859/-programmatically-change-log-level-in-log4j2Amish
I've edited the answer to add some clarification around the Configurator class.Maddock
One thing to be clear about when @Maddock mentions this is not part of the public api, you should be careful using this as a general solution as log4j could change the method signature in a newer version. Unless you wrap this in a util class, I only recommend using this solution as a temporary fix until you find a more general solution to silence noisy logs. Perhaps better log4j configuration management for each app.Hydra
Once it reaches a decade or so in the wild, you can bet your guns they consider this a "public API". Use the one-liner. Move on.Circus
Make sure you don't do what I did - tried all the ways mentioned on this page, none worked, then realised I had this in my log4j2.xml: <AppenderRef ref="File" level="debug"/> - you can't override that! (Just removed the level attribute and then Configurator.setRootLevel(Level.TRACE) worked)Basinet
@Silver The way I understand it Log4j2 is separating configuration and execution. A logger is an executing instance. You can even set a log level directly in a logger, but when doing so you're limited to existing loggers (loggers that already have been instantiated), and also your direct changes are volatile because configuration is the authoritative source, as we see above.Inexpugnable
R
74

The accepted answer by @slaadvak did not work for me for Log4j2 2.8.2. The following did.

To change the log Level universally use:

Configurator.setAllLevels(LogManager.getRootLogger().getName(), level);

To change the log Level for only the current class, use:

Configurator.setLevel(LogManager.getLogger(CallingClass.class).getName(), level);
Riehl answered 21/6, 2017 at 14:19 Comment(4)
Got my vote because you pulled the logger names from the loggers themselves rather than hard coding the name as a string.Olden
Though this solution worked for console logging, for rollingFile usage, I had to set proper ThresholdFilter value to make sure the change was reflecting.Stannite
@Stannite can you elaborate a bit? Maybe with your own answer with some code? I'm struggling as well...Gizela
@Matthieu, Add the threshold filter levels in rolling file config. For example, if you are using yaml config then set ThresholdFilter: level: TRACE in your log config yaml file.Stannite
B
20

I found a good answer here: https://garygregory.wordpress.com/2016/01/11/changing-log-levels-in-log4j2/

You can use org.apache.logging.log4j.core.config.Configurator to set the level for a specific logger.

Logger logger = LogManager.getLogger(Test.class);
Configurator.setLevel(logger.getName(), Level.DEBUG);
Bromal answered 18/1, 2017 at 10:41 Comment(1)
This answer shows the same solution, as well as how to set it for the root logger--which is sometimes useful: https://mcmap.net/q/144859/-programmatically-change-log-level-in-log4j2Amish
D
18

If you want to change a single specific logger level (not the root logger or loggers configured in the configuration file) you can do this:

public static void setLevel(Logger logger, Level level) {
    final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
    final Configuration config = ctx.getConfiguration();

    LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
    LoggerConfig specificConfig = loggerConfig;

    // We need a specific configuration for this logger,
    // otherwise we would change the level of all other loggers
    // having the original configuration as parent as well

    if (!loggerConfig.getName().equals(logger.getName())) {
        specificConfig = new LoggerConfig(logger.getName(), level, true);
        specificConfig.setParent(loggerConfig);
        config.addLogger(logger.getName(), specificConfig);
    }
    specificConfig.setLevel(level);
    ctx.updateLoggers();
}
Donell answered 14/8, 2015 at 21:58 Comment(5)
This didn't affect my logger at all. I used setLevel(logger, Level.ERROR); and logger.debug statements still printed. My log4j2.xml file is at pastebin.com/fcbV2mTWIdiopathy
I updated the code. Let me know if there are any issues with it.Restrict
In log4j 2.7 LoggerContext hasn't a getConfiguration() method, see logging.apache.org/log4j/2.x/log4j-api/apidocs/index.html?org/…Elora
log4j-core-2.7.jar has and can be used as final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); final Configuration configuration = ctx.getConfiguration();Notwithstanding
After seeing this, I am thinking.. "Maybe they do not want us to change the level in runtime?"Sublapsarianism
L
6

Most of the answers by default assume that logging has to be additive. But say that some package is generating lot of logs and you want to turn off logging for that particular logger only. Here is the code that I used to get it working

    public class LogConfigManager {

    public void setLogLevel(String loggerName, String level) {
        Level newLevel = Level.valueOf(level);
        LoggerContext logContext = (LoggerContext) LogManager.getContext(false);
        Configuration configuration = logContext.getConfiguration();
        LoggerConfig loggerConfig = configuration.getLoggerConfig(loggerName);
        // getLoggerConfig("a.b.c") could return logger for "a.b" if there is no logger for "a.b.c"
        if (loggerConfig.getName().equalsIgnoreCase(loggerName)) {
            loggerConfig.setLevel(newLevel);
            log.info("Changed logger level for {} to {} ", loggerName, newLevel);
        } else {
            // create a new config.
            loggerConfig = new LoggerConfig(loggerName, newLevel, false);
            log.info("Adding config for: {} with level: {}", loggerConfig, newLevel);
            configuration.addLogger(loggerName, loggerConfig);


            LoggerConfig parentConfig = loggerConfig.getParent();
            do {
                for (Map.Entry<String, Appender> entry : parentConfig.getAppenders().entrySet()) {
                    loggerConfig.addAppender(entry.getValue(), null, null);
                }
                parentConfig = parentConfig.getParent();
            } while (null != parentConfig && parentConfig.isAdditive());
        }
        logContext.updateLoggers();
    }
}

A test case for the same

public class LogConfigManagerTest {
    @Test
    public void testLogChange() throws IOException {
        LogConfigManager logConfigManager = new LogConfigManager();
        File file = new File("logs/server.log");
        Files.write(file.toPath(), new byte[0], StandardOpenOption.TRUNCATE_EXISTING);
        Logger logger = LoggerFactory.getLogger("a.b.c");
        logger.debug("Marvel-1");
        logConfigManager.setLogLevel("a.b.c", "debug");
        logger.debug("DC-1");
        // Parent logger level should remain same
        LoggerFactory.getLogger("a.b").debug("Marvel-2");
        logConfigManager.setLogLevel("a.b.c", "info");
        logger.debug("Marvel-3");
        // Flush everything
        LogManager.shutdown();

        String content = Files.readAllLines(file.toPath()).stream().reduce((s1, s2) -> s1 + "\t" + s2).orElse(null);
        Assert.assertEquals(content, "DC-1");
    }
}

Assuming following log4j2.xml is in classpath

<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns="http://logging.apache.org/log4j/2.0/config">

    <Appenders>
        <File name="FILE" fileName="logs/server.log" append="true">
            <PatternLayout pattern="%m%n"/>
        </File>
        <Console name="STDOUT" target="SYSTEM_OUT">
            <PatternLayout pattern="%m%n"/>
        </Console>
    </Appenders>

    <Loggers>
        <AsyncLogger name="a.b" level="info">
            <AppenderRef ref="STDOUT"/>
            <AppenderRef ref="FILE"/>
        </AsyncLogger>

        <AsyncRoot level="info">
            <AppenderRef ref="STDOUT"/>
        </AsyncRoot>
    </Loggers>

</Configuration>
Lindell answered 28/3, 2018 at 12:59 Comment(0)
J
5

For those of you still struggeling with this, I had to add the classloader to the "getContext()" call:

  log.info("Modifying Log level! (maybe)");
  LoggerContext ctx = (LoggerContext) LogManager.getContext(this.getClass().getClassLoader(), false);
  Configuration config = ctx.getConfiguration();
  LoggerConfig loggerConfig = config.getLoggerConfig("com.cat.barrel");
  loggerConfig.setLevel(org.apache.logging.log4j.Level.TRACE);
  ctx.updateLoggers();

I added a jvm argument: -Dlog4j.debug to my test. This does some verbose logging for log4j. I noticed that the final LogManager was not the one that I was using. Bam, add the class loader and you are off to the races.

Julienne answered 4/12, 2020 at 21:52 Comment(0)
H
4

The programmatic approach is rather intrusive. Perhaps you should check JMX support given by Log4J2:

  1. Enable the JMX port in your application start up:

    -Dcom.sun.management.jmxremote.port=[port_num]

  2. Use any of the available JMX clients (the JVM provides one in JAVA_HOME/bin/jconsole.exe) while executing your application.

  3. In JConsole look for the "org.apache.logging.log4j2.Loggers" bean

  4. Finally change the level of your logger

The thing that I like most of this is that you don´t have to modify your code or configuration for managing this. It´s all external and transparent.

More info: http://logging.apache.org/log4j/2.x/manual/jmx.html

Hopeless answered 24/5, 2014 at 14:49 Comment(3)
How is starting a client connecting to a JMX server just to change a log level less intrusive than calling a few methods?Gizela
Calling a few methods, from where? Code that is error prone and you have to maintain? Checking and modifying parameters/metrics at runtime is done externally in any serious application (take Spring Boot Actuator as an example). You rely on proven solutions to do such things, you don't reinvent the wheel. DRY/KISSHopeless
If you already have JMX configured and it is secured and you are already using JMX, this is reasonable. However, if you are not already using JMX, or you do not have your JMX configuration secured, this is a bad idea.Kling
A
2

One un-usual way i found to do is to create two separate file with different logging level.
For example. log4j2.xml and log4j-debug.xml Now change the configuration from this files.
Sample Code:

ConfigurationFactory configFactory = XmlConfigurationFactory.getInstance();
            ConfigurationFactory.setConfigurationFactory(configFactory);
            LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
            ClassLoader classloader = Thread.currentThread().getContextClassLoader();
            InputStream inputStream = classloader.getResourceAsStream(logFileName);
            ConfigurationSource configurationSource = new ConfigurationSource(inputStream);

            ctx.start(configFactory.getConfiguration(ctx, configurationSource));
Almire answered 15/11, 2017 at 11:48 Comment(0)
H
-4

As of 2023 the above do not seem to work (or I will say at least did not seem to work for me), what did work was the following

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

If you want to see how to do this on a per request basis see my comment on post Change priority level in log4j per request

Holbein answered 11/2, 2023 at 20:58 Comment(2)
The question asked how to change log level in Logj42 Core, your answer shows how to change log level in Logback.Stretcherbearer
Your solution might be valid for this question, but in this context it just does not compile. The downvoter probably shared my opinion.Stretcherbearer

© 2022 - 2024 — McMap. All rights reserved.