My goal is to programmatically set the log4j2 log level. It's working in my IDE, but once the program is built into a JAR and deployed on another machine the procedure has no effect. What can be the problem?
This is the code:
LoggerContext ctx = (LoggerContext)LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
LoggerConfig rootLoggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
Level level = Level.ERROR;
rootLoggerConfig.setLevel(level);
// show all available loggers and set them to the new level:
Map<String, LoggerConfig> loggers = config.getLoggers();
System.out.println("\nConfigured loggers:");
for (Map.Entry<String, LoggerConfig> entry : loggers.entrySet())
{
System.out.println("Logger: " + entry.getKey() + " Level: " + entry.getValue().getLevel());
LoggerConfig loggerConfig = config.getLoggerConfig(entry.getKey());
loggerConfig.setLevel(level);
}
System.out.println("\nLog4j2 configuration file location: " +
((org.apache.logging.log4j.core.LoggerContext)LogManager.getContext(false))
.getConfiguration().getConfigurationSource().getLocation());
ctx.updateLoggers();
_log.fatal("LogLevel changed to " + newLogLevel);
_log.fatal("Current logger is " + _log.getName() + " with level " + _log.getLevel());
_log.info("You shouldn't see this");
The output on my IDE machine is like this:
Configured loggers:
Logger: Level: ERROR
Logger: com.newhighs Level: INFO
Log4j2 configuration file location: /home/mark/tremendous/Bison/trunk/target/classes/log4j2.properties
2025-05-12 15:07:35.815 FATAL [main] Bison - LogLevel changed to ERROR
2025-05-12 15:07:35.816 FATAL [main] Bison - Current logger is com.newhighs.bison.Bison with level ERROR
While on the other it is like this:
Configured loggers:
Logger: Level: ERROR
Logger: com.newhighs Level: INFO
Log4j2 configuration file location: jar:file:/home/mark/bison/jar/bison-1.0.8794-SNAPSHOT-jar-with-dependencies.jar!/log4j2.properties
2025-05-12 15:08:32.844 FATAL Bison - LogLevel changed to ERROR
2025-05-12 15:08:32.844 FATAL Bison - Current logger is com.newhighs.bison.Bison with level INFO
2025-05-12 15:08:32.844 INFO Bison - You shouldn't see this
The log4j2.properties files are identical, it's just that one is on the regular filesystem, the other in the JAR. Why is the log level still INFO on the second machine?
(For completeness, here's the contents of log4j2.properties)
# Log4j2.properties of Bison
status = warn
appender.console.type = Console
appender.console.name = LogToConsole
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %c{1} - %msg%n
# Rotate log file
appender.rolling.type = RollingFile
appender.rolling.name = LogToRollingFile
appender.rolling.fileName = logs/bison.log
appender.rolling.filePattern = logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.rolling.policies.size.size=10MB
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.max = 10
# Log to console and rolling file
logger.app.name = com.newhighs
# put this on debug for debug level
logger.app.level = INFO
logger.app.additivity = false
logger.app.appenderRef.rolling.ref = LogToRollingFile
logger.app.appenderRef.console.ref = LogToConsole
rootLogger.level = INFO
rootLogger.appenderRef.stdout.ref = LogToConsole
And also for completeness: I checked using the JAR on the IDE machine. Same result as on the other machine. For some reason it doesn't work as expected when running from the JAR.
TL;DR: Don't use the Maven Assembly Plugin to create fat JARs. Instead, use the Spring Boot Maven Plugin or similar tooling that does not modify the original dependency JARs.
The issue you're encountering stems from how you're packaging your application.
Your JAR file name ending with jar-with-dependencies
suggests you're using the Maven Assembly Plugin to create a fat JAR. Unfortunately, this breaks the expected behavior of certain libraries—notably Log4j 2.
See this related question for more background: ➡️ Log4j implementation not found in fat jar
Log4j 2 uses a multi-release JARs to support caller detection and other advanced features on Java 9+. When you use the Maven Assembly Plugin to build a fat JAR, it flattens the JAR structure and disables the multi-release capability. As a result:
-Dlog4j2.statusLoggerLevel=WARN
, you'll see:WARN Runtime environment or build system does not support multi-release JARs. This will impact location-based features.
Log4j2 supports multiple logger contexts, and the default mechanism to select the correct context (ClassLoaderContextSelector
) depends on the caller class—as documented in log4j2.contextSelector
.
If caller detection fails (e.g. due to JAR repackaging that breaks multi-release JARs), Log4j2 cannot determine the correct context, and method calls like:
LogManager.getLogger();
LogManager.getLogger("com.example.Main");
LogManager.getContext(false);
will fail to find a classloader and will use the "Default" context. This is the one you configured programmatically.
In contrast, these calls explicitly use the logger context for the given class or classloader:
LogManager.getLogger(Main.class);
LogManager.getContext(Main.class.getClassLoader(), false);
If you're using one of these forms (which is likely), your logger ends up using a different logger context than the one you configured. This leads to symptoms like your configuration seemingly being ignored.
Use a JAR-packaging tool that does not flatten dependencies—e.g., the Spring Boot Maven Plugin:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
📚 See also: How do I create a single-JAR application containing Log4j Core?