Log4j2 does not change logging level at runtime - java

Yes, I've read all the related questions. I am using log4j2 (tried both version 2.4 and updating to latest, version 2.6.2).
I have a small utility program for customers. I'm keen to keep exposed configurations at minimum. But for problematic cases, I'd also want to add a -debug flag to enable debug logs at runtime.
Here is my code to enable debug logging
private static void enableDebugLogs(){
LoggerContext ctx = (LoggerContext) LogManager.getContext();
LoggerConfig log = ctx.getConfiguration().getRootLogger();
System.out.println(log.getLevel()); // INFO
log.setLevel(Level.DEBUG);
System.out.println(log.getLevel()); // DEBUG
ctx.updateLoggers();
System.out.println(ctx.getRootLogger().getLevel()); // DEBUG, hey it works, right?
}
But it does not actually work with any of these cases:
enableDebugLogs();
logger.debug("Debug mode on"); // static, already made logger. Level did not change
LogManager.getLogger(Main.class).debug("Debug"); // Nope, not printing
Logger root = LogManager.getRootLogger();
root.info("Level: " + root.getLevel()); // Level: INFO, should be DEBUG
The utility program is finished usually in less than 30 seconds, so the change should be instant. Here is the log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level - %msg%n"/>
</Console>
<RollingFile name="File" fileName="program_name.log" filePattern="program_name-archived.log">
<PatternLayout>
<Pattern>%d{HH:mm:ss.SSS} %-5level - %msg%n</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="10 KB" />
</Policies>
<DefaultRolloverStrategy min="1" max="1"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
Is the problem with usings AppenderRefs? Can I somehow tell the Appenders to update logging level from Root logger?

Found the real issue. Had to use:
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
instead of
LoggerContext ctx = (LoggerContext) LogManager.getContext();
API stating the difference being "returns the LoggerContext" and "returns the current LoggerContext". And I clearly missed this bit of information for the version without boolean parameter:
"WARNING - The LoggerContext returned by this method may not be the LoggerContext used to create a Logger for the calling class."

You can change switch the logging configuration between two or multiple log4j2.xml files.
For example, create two log4j2.xml files with different configurations. log4j2.xml & log4j2-debug.xml and pass it to below 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));

To reconfigure log4j2 after it's initialization, the documentation give you two ways :
Using the config file, you can enable the automatic reconfiguration (it's the preferred way) so that modifying the file after initialization will be reflected on your runtime : http://logging.apache.org/log4j/2.x/manual/configuration.html#AutomaticReconfiguration
Using ConfigurationBuilder you can reconfigure log4j programatically (I think it's what you are looking for), see the "Reconfigure Log4j Using ConfigurationBuilder with the Configurator" paragraph of this page : http://logging.apache.org/log4j/2.x/manual/customconfig.html

In addition to the above, to debug log4j initialization, set <Configuration status="trace" in the beginning of the configuration file. Log4j2's internal status logs will print to the console. This may help troubleshooting.

Here is a little known fact.
If you set your ROOT_LOGGER level in your log4j2.xml or equivalent properties to say DEBUG, then setting the level dynamically to something lower did not work for me; in other words, setting dynamically had no effect.
ONLY when I change the setting in log4j2.xml to TRACE and then using the below to change the log level did it work for me:
Level level = Level.valueOf(this.consoleLogLevel.toUpperCase());
//Dynamically set the log level for ALL loggers
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
loggerConfig.setLevel(level);
ctx.updateLoggers();
And here is my simple configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="TRACE">
<AppenderRef level="TRACE" ref="Console" />
</Root>
</Loggers>
</Configuration>
And here is a TestNG test you can refactor to verify the above:
#Test
public void givenCut_whenLoggingWithERRORLevel_LoggingOutputIsAtERRORLevelAndHigherOnly() {
try {
lcaProperties.setLogLevel(Level.ERROR.name());
GlobalLogger globalLogger = GlobalLogger.builder().lcaServiceProperties(lcaProperties).build();
assertNotNull(globalLogger.getConsoleLogger());
assertNotNull(globalLogger.getFunctionalDbLogger());
assertEquals(globalLogger.getConsoleLogger().getLevel(), Level.ERROR);
assertEquals(log.getLevel(), Level.ERROR);
System.out.println("log.getLogLevel() = " + log.getLevel());
System.out.println("globalLogger.getConsoleLogger().getLevel() = " + globalLogger.getConsoleLogger().getLevel());
log.fatal("logLevel::"+log.getLevel()+"::log.fatal");
log.error("logLevel::"+log.getLevel()+"::log.error");
log.warn("logLevel::"+log.getLevel()+"::log.warn");
log.info("logLevel::"+log.getLevel()+"::log.info");
log.debug("logLevel::"+log.getLevel()+"::log.debug");
log.trace("logLevel::"+log.getLevel()+"::log.trace");
globalLogger.getConsoleLogger().fatal("logLevel::"+globalLogger.getConsoleLogger().getLevel()+"::globalLogger.getConsoleLogger().fatal");
globalLogger.getConsoleLogger().error("logLevel::"+globalLogger.getConsoleLogger().getLevel()+"::globalLogger.getConsoleLogger().debug");
globalLogger.getConsoleLogger().warn("logLevel::"+globalLogger.getConsoleLogger().getLevel()+"::globalLogger.getConsoleLogger().debug");
globalLogger.getConsoleLogger().info("logLevel::"+globalLogger.getConsoleLogger().getLevel()+"::globalLogger.getConsoleLogger().debug");
globalLogger.getConsoleLogger().debug("logLevel::"+globalLogger.getConsoleLogger().getLevel()+"::globalLogger.getConsoleLogger().debug");
globalLogger.getConsoleLogger().trace("logLevel::"+globalLogger.getConsoleLogger().getLevel()+"globalLogger.getConsoleLogger()::log.trace");
} catch (Exception e) {
fail();
log.error(e);
}
}

Related

How to remove the ConsoleLogger programmatically from log4j2?

I'm having a global logger config that I want to inherit throughout my projects:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="%d %p %c{1.}: %m%n"/>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
</Console>
<RollingRandomAccessFile name="APP" fileName="/logs/application.logs" filePattern="/logs/application-%d{yyyy-MM-dd}.log.gz">
<PatternLayout pattern="%d %p %c{1.}: %m%n"/>
<Filters>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="APP" />
<AppenderRef ref="CONSOLE" />
</Root>
</Loggers>
</Configuration>
Problem: only in production, I want to remove one of the loggers, the CONSOLE appender.
As log4j2.xml configuration files do not support conditionals, I thought about removing the console appender programmatically:
final LoggerContext context = (LoggerContext) LogManager.getContext(false);
final Configuration config = context.getConfiguration();
System.out.println(config.getAppenders());
Problem: this prints only {DefaultConsole-2=DefaultConsole-2}
Question: why can't I see the APP or CONSOLE appender here? And moreover, how can I remove the console appender then?
Maybe it is possible to intercept the log4j context loading somehow, so that I could skip the CONSOLE appender programmatically?
Sidenote: I'm logging as follows, which should in production only go to the APP appender, not to console.
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
LOGGER.info("test");
For <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" /> in Console Appender you can add environment variable <ThresholdFilter level="${CONSOLE_LOG_LEVEL}" onMatch="ACCEPT" onMismatch="DENY" /> or something similar and set this variable to OFF. I faces with this problem too and it works for me.
This also works with vm args as follows:
<ThresholdFilter level="${sys:console.log.level}" ... />
When starting the app: java -jar -Dconsole.log.level=ERROR
Sidenote: programatically, removing the console appender would also work:
public static void main(String[] args) {
ctx = SpringApplication.run(MyApp.class, args);
final LoggerContext context = (LoggerContext) LogManager.getContext(false);
final Configuration config = context.getConfiguration();
LoggerConfig loggerConfig = config.getLoggerConfig("loggerName");
loggerConfig.removeAppender("CONSOLE");
context.updateLoggers();
}
Most important for the programmatic approach is that the SpringApplication context must be initialized before! Otherwise the configured loggers are not visible!

Log4j2 - Config is found, but not working correctly

Log4j is finding my config, because as soon as I delete it I get an error message saying it couldn't find one, however it's properties are not reflected when logging.
log4j2.properties:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="TRACE">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Test.java:
public class Test {
private static Logger logger = LogManager.getLogger(Test.class);
public static void main(String[] args) throws Exception {
logger.info("test");
logger.fatal(logger.getLevel());
}
}
Output:
20:19:31.848 [main] FATAL io.rj93.sarcasm.examples.CnnSentenceClassificationExample - ERROR
As you can see, the logger is returning the level to be ERROR when it is set to INFO, and the time format is including the milliseconds even though it has been removed.
The config file is taken from the log4j website, with only minor changes (the two mentioned, and status="TRACE")
I am using version 2.8.1.
You use a log4j2.properties file with a XML configuration inside it.
It is not consistent.
The log4J initialization doesn't recognize the format used as a properties format. So it uses the default log4J configuration that specifies ERROR level for the root logger.
Simply rename log4j2.properties to log4j2.xml and it should be fine.

Log4J2 and Tomee Plus 7.0.47 not working

According to here, Log4j2 should work with Tomcat7.0.47. I'm using TomEE Plus 7.0.47.
I have a webapplication deployed with a log4j2.xml in my web-inf/classes folder. This is the config:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="File" fileName="${sys:catalina.home}/logs/testapp.log">
<PatternLayout>
<pattern>%d %p %C{1.} [%t] %m%n</pattern>
</PatternLayout>
</File>
</Appenders>
<Loggers>
<Logger name="org.alex" level="TRACE" additivity="false">
<AppenderRef ref="File"/>
</Logger>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
I have a logger declared in a class with name org.alex.util.JSON:
private static final Logger LOG = LoggerFactory.getLogger(JSON.class);
I'm using slf4j-api 1.7.5, and have the following libs added to the tomcat lib:
slf4j-api-1.7.5.jar
log4j-api-2.0-rc1.jar
log4j-core-2.0-rc1.jar
log4j-slf4j-impl-2.0-rc1.jar
If I change the Configuration status to TRACE, I can see my configuration file being picked up and configuration happens as expected. Also I can see the MBeans being added.
However, there's not one logging statement ending up in my logfile. I debugged into the log4j2 Logger, and see that the isEnabled(...) method returns false because the logger (com.alex.util.JSON) has the level "ERROR" set, while the configuration set the package org.alex to TRACE.
Further investigation shows it uses a DefaultConfiguration configured for level=ERROR, and only root is configured. I'm thinking of a classloader issue, but I can't seem to figure out what the cause is and how to solve it.
Does anyone know what I'm doing wrong?
This should work on trunk
Btw saw log4j2 has hacks for tomcat and since tomee wraps classloaders not sure they work as expected...
This is very strange. Please raise a ticket for this in the Log4j2 issue tracker so the Log4j team can take a look.
The problem may go away if you put the jar files you mentioned inside WEB-INF/lib instead of in Tomcat's lib folder.
To be comple log4j2 relies on servletcontainerinitializer which are called after ejb and app scanning so ejbs can be loaded too early. Doing the same with a tomcat context listener would make it working better

How to configure Log4J2

I have spent a couple hours now trying to figure this out and am at a complete loss. I find the new configuration process unnecessarily complex.
I have a Servlet with a web.xml file with the following:
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>file:///etc/myApp/log4j2.xml</param-value>
</context-param>
It doesn't seem to have any effect. I am using Tomcat 7.0.42, and I have removed all references to log4j*.jar in the catalina.properties file. The logs in my app are still being sent, but they are just being sent to System.out with none of the formatting I specified.
So, I tried to just do it by hand on my own:
InputStream in =
new FileInputStream(new File("/etc/myApp/log4j2.xml"));
ConfigurationSource source = new ConfigurationSource(in);
ConfigurationFactory factory = new XMLConfigurationFactory();
Configuration configuration = factory.getConfiguration(source);
LoggerContext context = (LoggerContext) LogManager.getContext();
context.start(configuration);
context.updateLoggers();
Logger.getLogger("Test").log(Level.INFO, "This is a logging message.");
First, this seems entirely convoluted. Clearly there exists some code that will search for different files and assume file types based on their extensions via the "log4jConfiguration" property, so why isn't there a LogManger.reconfigure(String) that has the same effect?
Second, this has no effect either. Again, the log is printed to System.out, and none of the requested formatting is being done.
Here is the contents of my log4j2.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="LogFile" fileName="/var/log/myApp/myApp.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c{1.} [%t] %m%n"/>
</File>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console" level="DEBUG"/>
<AppenderRef ref="LogFile" level="DEBUG"/>
</Root>
</Loggers>
</Configuration>
The output in both cases comes out something like:
Dec 03, 2013 6:06:45 PM test.Test main
INFO: This is a logging message.
Thanks in advance,
John
EDIT: This is actually working. It appears that I was missing, "log4j-jcl-2.0-beta9.jar". Remko Popma's answer works as well, if the "context-param" above is not working.
There are many possible scenarios with webapp logging, which makes configuration more complex than the ideal. Putting the log4j2 jars and the config file in your webapp's classpath should work. Did you see the manual page for log4j2 use in web apps? If the issue remains, please file a log4j2 jira ticket.
The LoggerContextalready has a method setConfigLocation that takes in a URI as parameter. It baffles me why there isn't a similar method that takes an InputStream as parameter. Instead we have to deal with the most convoluted and messy way of loading our log4j2.xml that is not on the classpath.

Log4j 2.0 - No Logs appearing in the Log Files - Trying Multiple Loggers in the same Class using log4j2.xml

Below is the log4j2.xml file that I have created. I have configured async_file.log for Asynchronous Logging and regular_file.log for regular and synchronous logging. The problem is that the log files get created, but the size of the files is zero and with no logs. All logs are coming to server.log file (JBOSS) and not to the 2 files that I had got configured for (async_file.log and regular_file.log).
Please let me know why the logs are NOT going to the log files that I have configured. Please help me with this or give me some direction or hint.
I am calling the two different loggers in the same class file by name DCLASS as shown below:
private static final transient Logger LOG = Logger.getLogger(DCLASS.class);
private static final transient Logger ASYNC_LOG = Logger.getLogger("ASYNC");
I have included the following jars in the Class Path:
1. log4j-api-2.0-beta8.jar
2. log4j-core-2.0-beta8.jar
3. disruptor-3.0.0.beta1.jar
My log4j2.xml is as below:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="INFO">
<appenders>
<!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
<FastFile name="AsyncFastFile" fileName="../standalone/log/async_file.log"
immediateFlush="false" append="true">
<PatternLayout>
<pattern>%d %p %class{1.} [%t] %location %m %ex%n</pattern>
</PatternLayout>
</FastFile>
<FastFile name="FastFile" fileName="../standalone/log/regular_file.log"
immediateFlush="true" append="true">
<PatternLayout>
<pattern>%d %p %class{1.} [%t] %location %m %ex%n</pattern>
</PatternLayout>
</FastFile>
</appenders>
<loggers>
<!-- pattern layout actually uses location, so we need to include it -->
<asyncLogger name="ASYNC" level="trace" includeLocation="true">
<appender-ref ref="AsyncFastFile"/>
</asyncLogger>
<root level="info" includeLocation="true">
<appender-ref ref="FastFile"/>
</root>
</loggers>
</configuration>
The reason why the logs were not coming to the log files is because, I was using 'Logger' instead of 'LogManager'.
In the code, I had
private static final transient Logger ASYNC_LOG = Logger.getLogger("ASYNC");
The code should have been
private static final transient Logger ASYNC_LOG = Logmanager.getLogger("ASYNC");
When it is 'logger', then the compiler is looking into 'Log4j API' and when it is 'LogManager' it is looking into 'Log4j2 API'. Since I have configured everything to use Log4j2, by changing logger to LogManager, the logs started coming to the log files as expected.

Categories

Resources