We use log4j 1.2.x for logging in our product and are looking to migrate to log4j 2.x in near future. One of the functionality we have implemented is to log the system information and other important parameters on every new roll-over logfile that gets generated. The way we implemented in log4j 1.2.x is that we have extended RollingFileAppender
class of log4j and have overridden the rollOver()
method, below is the part snippet of the implementation
@Override
public void rollOver() {
super.rollOver(); //We are not modifying it's default functionality but as soon as rollOver happens we apply our logic
//
// Logic to log required system properties and important parameters.
//
}
Now as we want to migrate to log4j2 we are looking at a new solution to achieve same functionality. But as I see the source code for log4j2 it is very different from older source code. The RollingFileAppender
class does not contain rollover()
method as it has been moved to RollingManagerhelper
and it has been set to private
as-well.
Developing a complete new package and extending/implementing some abstract/helper classes from log4j2 is one of the possible solution for us but that would require a lot of coding/copying as we do not modify what RollingFileAppender
does rather we only need small extension to it. Is there a simple solution to it?
UPDATE
I created a custom lookup according to the suggestion in answers and below is how I created it;
@Plugin(name = "property", category = StrLookup.CATEGORY)
public class CustomLookup extends AbstractLookup {
private static AtomicLong aLong = new AtomicLong(0);
@Override
public String lookup(LogEvent event, String key) {
if (aLong.getAndIncrement() == 0) {
return "this was first call";
}
if (key.equalsIgnoreCase("customKey")) {
return getCustomHeader();
} else {
return "non existing key";
}
}
private static String getCustomHeader() {
// Implementation of custom header
return "custom header string";
}}
But this did not work as mentioned; this always prints this was first call
in the header. I also tried putting breakoint on the first if
condition and what I noticed was that it only gets called once. So what I fear is that the customLookup class only gets initialized on the startup when log4j2 is initialising its properties from xml config. I don't know how else I could implemented this custom lookup class.
UPDATE 2
After the above implementation I tried it in bit different way which is as below;
private static AtomicLong aLong = new AtomicLong(0);
@Override
public String lookup(LogEvent event, String key) {
return getCustomHeader(key);
}
private static String getCustomHeader(final String key) {
if (aLong.getAndIncrement() == 0) {
return "this was first call";
}
if (key.equalsIgnoreCase("customKey")) {
// Implementation for customKey
return "This is custom header";
} else {
return "non existing key";
}
}
But this does the same thing as-well. log4j2 creates the headers at while initialising from its xml config file and then uses the headers from memory. The return
value of overridden lookup()
method can not be changed dynamically as it only gets called during initialisation. Any more help would be highly appreciated.
RollingFileAppender extends FileAppender to backup the log files when they reach a certain size. The log4j extras companion includes alternatives which should be considered for new deployments and which are discussed in the documentation for org.
An alternative to using the built-in lookups is to create a custom lookup. This can be accomplished in a few lines of code with a log4j2 plugin. Your custom lookup then provides the exact value you want to show in the file header at each roll over.
The plugin code would look something like this:
package com.mycompany;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.lookup.AbstractLookup;
import org.apache.logging.log4j.core.lookup.StrLookup;
/**
* Looks up keys from a class SomeClass which has access to all
* information you want to provide in the log file header at rollover.
*/
@Plugin(name = "setu", category = StrLookup.CATEGORY)
public class SetuLookup extends AbstractLookup {
/**
* Looks up the value of the specified key by invoking a
* static method on SomeClass.
*
* @param event The current LogEvent (ignored by this StrLookup).
* @param key the key to be looked up, may be null
* @return The value of the specified key.
*/
@Override
public String lookup(final LogEvent event, final String key) {
return com.mycompany.SomeClass.getValue(key);
}
}
Then, in your configuration you can use the header of the pattern layout to output this at every rollover:
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}.log.gz">
<!-- use custom lookups to access arbitrary internal system info -->
<PatternLayout header="${setu:key1} ${setu:key2}">
<Pattern>%d %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
</Policies>
</RollingFile>
The log4j2 manual has details on building/deploying custom plugins. Brief summary:
The easiest way is to build your jar with Maven; this will cause the log4j2 annotation processor to produce a binary index file in the jar so your plugin can be found quickly by log4j2.
The alternative is to specify the package name of your plugin class in your log4j2.xml configuration's packages
attribute:
<Configuration status="warn" packages="com.mycompany">
...
UPDATE: Note that in your lookup implementation you can get as creative as necessary. For example:
package com.mycompany;
public class SomeClass {
private static AtomicLong count = new AtomicLong(0);
public static String getValue(final String key) {
if (count.getAndIncrement() == 0) { // is this the first call?
return ""; // don't output a value at system startup
}
if ("FULL".equals(key)) {
// returns info to shown on rollover, nicely formatted
return fullyFormattedHeader();
}
return singleValue(key);
}
....
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With