Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper method for registering custom LoggerFactory for Log4j 1.2?

I'm working on a web application that uses Log4j (1.2.16) logging through out. To avoid having to edit a ton of files, I am trying to hook in a custom LoggerFactory implementation that protects against log forging.

It appears one can set the log4j.loggerFactory configuration setting (project uses a property file method) to specify the logger factory to use. However, this does not appear to work. After examining the source code for Log4j, it appears that the property is never really used, even though it is read by the PropertyConfigurator class.

Examining more of the Log4j source, it appears the only way to achieve what I want, I have to create custom sub-classes of Log4j classes. Is this the only way?

The following represents what I had to do in my web application context listener to initialize Log4j so my custom logger factory will be used:

package com.example.myapp.log4j;

import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.*;

public class MyLog4jInitContextListener implements ServletContextListener
{
  public void contextInitialized(
      ServletContextEvent event
  ) {
    this.context = event.getServletContext();

    String file = context.getInitParameter("log4jConfiguration");
    if (file != null) {
      String prefix = context.getRealPath("/");
      String pathname = prefix+file;
      event.getServletContext().log("Initializing log4j with "+pathname);
      org.apache.log4j.LogManager.setRepositorySelector(
          new org.apache.log4j.spi.DefaultRepositorySelector(
            new MyHierarchy(
              new org.apache.log4j.spi.RootLogger(
                (org.apache.log4j.Level)org.apache.log4j.Level.INFO))), this);
      new MyPropertyConfigurator().doConfigure(
          pathname, org.apache.log4j.LogManager.getLoggerRepository());
    } else {
      event.getServletContext().log(
          "No log4jConfiguration parameter specified");
    }
  }

  public void contextDestroyed(
      ServletContextEvent event
  ) {
    this.context = null;
  }

  private ServletContext context = null;
}

I had to create a custom Hierarchy since it appears it hardcodes the default logging factory. This version makes sure my factory gets used when the single argument getLogger() method is called (which appears to happen via Logger.getLogger() -> LogManager.getLogger() -> Hierarchy.getLogger()):

MyHierarchy.java

package com.example.myapp.log4j;
import org.apache.log4j.Hierarchy;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggerFactory;

public class MyHierarchy extends Hierarchy
{
  public MyHierarchy(Logger root) { super(root); }
  @Override
  public Logger getLogger(String name) {
    return getLogger(name, defaultFactory);
  }
  private LoggerFactory defaultFactory = new MyLoggerFactory();
}

Not sure I need to customize PropertyConfigurator, but I did in case there is some execution path that actually uses the logger factory instance it keeps a reference to.

MyPropertyConfigurator.java

package com.example.myapp.log4j;
import org.apache.log4j.PropertyConfigurator;

public class MyPropertyConfigurator extends PropertyConfigurator
{
  public MyPropertyConfigurator() {
    loggerFactory = new MyLoggerFactory();
  }
  @Override
  protected void configureLoggerFactory(java.util.Properties props) {
  }
}

The following is my logger factory implementation. The MyEscapedLogger implementation is a subclass of Log4j's Logger class, but it overrides the forcedLog() protected method to escape characters in the message before calling the super version of the method.

MyLoggerFactory.java

package com.example.myapp.log4j;
import org.apache.log4j.spi.LoggerFactory;

public class MyLoggerFactory implements LoggerFactory
{
  public Logger makeNewLoggerInstance(String name) {
    return new MyEscapedLogger(name);
  }
}
like image 832
ewh Avatar asked Nov 01 '22 13:11

ewh


1 Answers

After searching for this for quite some time i found this really nice solution:

        final LoggerFactory loggerFactory = new LoggerFactory()
        {

            @Override
            public Logger makeNewLoggerInstance(String name)
            {
                return new MyCustomLogger(name);
                // or (if you don't need a custom Logger implementation):
                // Logger logger = new Logger(name);
                // logger.setSTUFF(...);
                // return logger;
            }

        };

        LoggerRepository rep = new Hierarchy(new RootLogger(Level.DEBUG))
        {

            @Override
            public Logger getLogger(String name)
            {
                return super.getLogger(name, loggerFactory);
            }

        };

        LogManager.setRepositorySelector(new DefaultRepositorySelector(rep), null);

I was using: log4j 1.2.17

like image 64
Frederic Leitenberger Avatar answered Nov 15 '22 05:11

Frederic Leitenberger