Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to customize log4j2 RollingFileAppender?

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.

like image 865
madteapot Avatar asked Dec 03 '14 14:12

madteapot


People also ask

What is the use of RollingFileAppender?

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.


1 Answers

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);
    }
    ....
}
like image 56
Remko Popma Avatar answered Sep 18 '22 11:09

Remko Popma