3
\$\begingroup\$

I'm trying to design and develop a singleton class that will handle resolution and configuration of log4j (not log4j2). The basic idea is to have a single class, reusable in any application, that handles log4j configuration and management.

I want the following features available:

  • Singleton, one instance, conserve memory
  • XML and DOMConfigurator
  • Ability to use a JVM Argument: -Dorg.jbjf.log4j=./etc/app-log4j.xml to configure the log4j during startup
  • Ability to return Logger objects using a getLog (String loggerName)
  • Configure the log4j framework based on an XML file that can be:

    • From a full/partial dir-path/filename.xml
    • From a CLASSPATH like URL /log4j.xml
  • Provide some basic getter/setter methods to manage different Logger objects as needed using Logger.name. Also, need a way to pass in a new XML file and re-configure the log4j framework.

package org.jbjf.core;

import java.io.File;
import java.net.URL;
import java.util.Enumeration;

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.xml.DOMConfigurator;

/**
 * <p>
 * The <code>RootLogger</code> is a simple log4j singleton class
 * that provides an easy management interface to configure, get, set, 
 * re-configure the log4j framework for your application.
 * </p>
 * <p>
 * <h3>Dependencies:</h3>
 * <ul>
 * <li>JRE/JDK 6(+)</li>
 * </ul>
 * </p>
 * <p>
 * <h3>Details</h3>
 * <hr>
 * <h4>Input Resources</h4>
 * <table border='1' width='100%'>
 * <thead>
 *  <tr>
 *      <td width='15%'>Method</td>
 *      <td width='1%'>&nbsp;</td>
 *      <td width='15%'>Usage</td>
 *      <td width='1%'>&nbsp;</td>
 *      <td>Description/Comments</td>
 *  </tr>
 * </thead>
 *  <tr valign='top'>
 *      <td>getEndTime/setEndTime</td>
 *      <td>&nbsp;</td>
 *      <td>Runtime</td>
 *      <td>&nbsp;</td>
 *      <td>
 *      Provides a JBJF ...
 *      </td>
 *  </tr>
 * </table>
 * </p>
 * <p>
 * <h3>Example(s)</h3>
 * <hr>
 * <pre>
 * </pre>
 * </p>
 * <p>
 * @author  Adym S. Lincoln<br>
 * Copyright (C) 2006-2017. JBJF, All rights reserved.
 * @version 1.0.0
 * @since   1.0.0
 * </p>
 * <p>
 * @see org.jbjf
 * </p>
 */

public final class RootLogger {

    /** 
     * Stores a fully qualified class name.  Used for debugging and 
     * auditing.
     * @since 1.0.0
     */
    public static final String ID              = RootLogger.class.getName ();

    /** 
     * Stores the class name, primarily used for debugging and so 
     * forth.  Used for debugging and auditing.
     * @since 1.0.0
     */
    @SuppressWarnings("unused")
    private static String      SHORT_NAME      = "RootLogger()";

    /** 
     * Stores a <code>SYSTEM IDENTITY HASHCODE</code>.  Used for
     * debugging and auditing.
     * @since 1.0.0
     */
    @SuppressWarnings("unused")
    private String             SYSTEM_IDENTITY = String.valueOf ( System.identityHashCode ( this ) );

    /**
     * Class property that contains the log4j XML file.  Defaults 
     * to DEFAULT_LOG4J_XML... ./etc/app-log4j.xml.
     */
    private static  String      log4jXML = null;

    /**
     * Class property that stores the log4j object/instance.
     */
    private static  Logger      log = null;

    /**
     * Class property that stores the singleton object/instance.
     */
    private static  RootLogger  instance = null;

    /**
     * Singleton constructor.
     */
    private RootLogger() {
    }

    /**
     * <p>
     * Global access point for the Log4j object/instance...
     * Logger   myLog = RootLogger.getInstance().getLog();
     * </p>
     * @return  The <code>RootLogger</code> object/instance.
     */
    public static   RootLogger  getInstance() {
        if( instance == null) {
            instance = new RootLogger();
            initInstance ();
        }
        return instance;
    }

    /**
     * <p>
     * Returns the current <code>Logger</code>.
     * </p>
     * @return  Returns the current <code>Logger</code>.
     */
    public static Logger getLog() {
        return log;
    }

    /**
     * <p>
     * Traditional getter that returns the Logger with the given
     * name...if no Logger is found, then null is returned.
     * </p>
     * @param theLoggerName The name of the Logger.
     * @return  A <code>Logger</code> if it exists or null.
     */
    public static Logger getLog ( String theLoggerName ) {
        Logger  ltheResults = null;
        if ( theLoggerName != null ) { 
            for (Enumeration loggers=LogManager.getCurrentLoggers(); loggers.hasMoreElements(); )  {
                Logger logger = (Logger) loggers.nextElement();
                if ( logger.getName ().equals ( theLoggerName ) ) { 
                    ltheResults = logger;
                }
                getLog().debug ( SHORT_NAME + ".setLog () - FOUND Logger......[" + logger.getName () + "]" );

            }

        }
        return ltheResults;
    }

    public static void setLog(Logger theLog) {
        log = theLog;
    }

    /**
     * <p>
     * A traditional setter method that allows you to set a specific
     * logger as the new logger object/instance.  By default, the
     * rootLogger is set when the instance is initialized.
     * </p>
     * @param theLoggerName  The name of the logger you wish to
     * promote as the new logger...if the name is NOT FOUND, then
     * the existing logger is left untouched.
     */
    public static void setLog ( String theLoggerName ) {
        if ( theLoggerName != null ) { 
            for (Enumeration<Logger> loggers=LogManager.getCurrentLoggers(); loggers.hasMoreElements(); )  {
                Logger logger = loggers.nextElement();
                if ( logger.getName ().equals ( theLoggerName ) ) { 
                    getLog().debug ( SHORT_NAME + ".setLog () - NEW Logger......[" + logger.getName () + "]" );
                    log = logger;
                    getLog().debug ( SHORT_NAME + ".setLog () - NEW Logger......[" + logger.getName () + "]" );
                }

            }

        }

    }

    /**
     * <p>
     * A secondary initialization that allows you to re-configure
     * the log4j framework using a different XML file.  The log4j
     * object/instance is reset to the new rootLogger defined in
     * the given XML file.  Unlike the startup initialization, this
     * initialization will only do the following search and resolve 
     * steps : 
     * </p>
     * <ul>
     * <li>Use the file path and filename.xml in getLog4jXML().</li> 
     * <li>Use getLog4jXML() as a resource on the CLASSPATH...
     * ClassLoader.getResource ( getLog4jXML() );</li> 
     * <li>Use getLog4jXML() as a resource next to this class...
     * this.class.getResource ( "/"+getLog4jXML() );</li> 
     * </ul>
     * <p>
     * If all of the configuration attempts fail, then the log4j
     * object/instance is left untouched.
     * </p>
     */
    public static void initInstance ( String theLog4jPath ) {
        // INFINITE LOOP : Causes an infinite loop...
        //setLog4jXML ( theLog4jPath );

        File            theLog4jFile = null;

        try {
            // FILE : First try and resolve and configure log4j 
            // using a direct file path/name.
            // 
            if ( getLog4jXML() != null ) { 
                theLog4jFile = new File ( getLog4jXML() );
                if ( theLog4jFile != null ) { 
                    if ( theLog4jFile.exists () ) { 
                        if ( theLog4jFile.canRead () ) { 
                            System.out.println ( SHORT_NAME + ".setLog () Log4j XML Path  [" + getLog4jXML() + "]" );
                            System.out.println ( SHORT_NAME + ".setLog () Log4j File Path [" + theLog4jFile.getAbsolutePath () + "]" );
                            DOMConfigurator.configureAndWatch ( getLog4jXML() ); 
                            log = Logger.getRootLogger ();

                            theLog4jFile = null;

                        }
                        else { 
                            System.out.println ( SHORT_NAME + ".setLog () Log4j XML Path  [" + getLog4jXML() + "] - CAN NOT BE READ!" );
                        }
                    }
                    else { 
                        System.out.println ( SHORT_NAME + ".setLog () Log4j XML Path  [" + getLog4jXML() + "] - DOES NOT EXIST!" );
                    }
                }
            }

            // URL : Second, try and resolve and configure log4j 
            // using a URL on the CLASSPATH.
            // 
            URL lclsLoaderResource = RootLogger.class.getClassLoader().getResource ( getLog4jXML() );
            if ( ( lclsLoaderResource != null ) ) { 
                System.out.println ( SHORT_NAME + ".setLog () classLoaderResource URL  [" + lclsLoaderResource.getFile () + "]" );
                DOMConfigurator.configure ( lclsLoaderResource );
                log = Logger.getRootLogger ();
            }

            // URL : Third, try and resolve and configure log4j 
            // using a URL on the class resource.
            // 
            URL lclassResource = RootLogger.class.getResource ( "/"+getLog4jXML() );
            if ( ( lclassResource != null ) ) { 
                System.out.println ( SHORT_NAME + ".setLog () classResource URL  [" + lclassResource.getFile () + "]" );
                DOMConfigurator.configure ( lclassResource );
                log = Logger.getRootLogger ();
            }

        }
        catch ( Exception lioXcp ) {
            lioXcp.printStackTrace();
        }

        getLog ().debug ( SHORT_NAME + ".setLog() - COMPLETE! [" + getLog ().getName () + "]" );

    }

    /**
     * <p>
     * The primary method that will do an exhaustive search and resolve
     * to identify and establish the log4j object/instance.  If all
     * search and resolves fails, then a default console and file
     * appender are created and assigned.
     * </p>
     * <ul>
     * <li>Use the file path and name.ext in getLog4jXML().</li> 
     * <li>Use getLog4jXML() as a resource on the CLASSPATH.</li> 
     * <li>Use getLog4jXML() as a resource next to this class.</li> 
     * <li>Use /log4j.xml as a resource next to this class.</li> 
     * <li>If all else, hard-code a console and single file appender (./jbjf-default.log) to teh rootLogger.</li> 
     * </ul>
     */
    public static void initInstance() {
        File            theLog4jFile = null;

        if ( System.getProperty ( JBJFBaseGlobals.JVM_LOG4J_ARGUMENT ) != null ) { 
            setLog4jXML ( System.getProperty ( JBJFBaseGlobals.JVM_LOG4J_ARGUMENT ) );
            System.out.println ( SHORT_NAME + ".initInstance () - JVM Argument......[" + JBJFBaseGlobals.JVM_LOG4J_ARGUMENT + "] Value [" + getLog4jXML () + "]" );
        }

        if ( getLog4jXML () == null ) { 
            setLog4jXML ( JBJFBaseGlobals.DEFAULT_LOG4J_XML );
            System.out.println ( SHORT_NAME + ".initInstance () - Default Log4j.....[" + JBJFBaseGlobals.DEFAULT_LOG4J_XML + "] Value [" + getLog4jXML () + "]" );
        }

        try {

            // FILE : First try and resolve and configure log4j 
            // using a direct file path/name.
            // 
            if ( getLog4jXML() != null ) { 
                theLog4jFile = new File ( getLog4jXML() );
                if ( theLog4jFile != null ) { 
                    if ( theLog4jFile.exists () ) { 
                        if ( theLog4jFile.canRead () ) { 
                            System.out.println ( SHORT_NAME + ".initInstance () Log4j XML Path  [" + getLog4jXML() + "]" );
                            System.out.println ( SHORT_NAME + ".initInstance () Log4j File Path [" + theLog4jFile.getAbsolutePath () + "]" );
                            DOMConfigurator.configureAndWatch ( getLog4jXML() ); 
                            log = Logger.getRootLogger ();

                        }
                        else { 
                            System.out.println ( SHORT_NAME + ".initInstance () Log4j XML Path  [" + getLog4jXML() + "] - CAN NOT BE READ!" );
                        }
                    }
                    else { 
                        System.out.println ( SHORT_NAME + ".initInstance () Log4j XML Path  [" + getLog4jXML() + "] - DOES NOT EXIST!" );
                    }
                }
            }

            // URL : Second, try and resolve and configure log4j 
            // using a URL on the CLASSPATH.
            // 
            URL lclsLoaderResource = RootLogger.class.getClassLoader().getResource ( getLog4jXML() );
            if ( ( lclsLoaderResource != null ) && ( log == null ) ) { 
                System.out.println ( SHORT_NAME + ".initInstance () classLoaderResource URL  [" + lclsLoaderResource.getFile () + "]" );
                DOMConfigurator.configure ( lclsLoaderResource );
                log = Logger.getRootLogger ();
            }

            // URL : Third, try and resolve and configure log4j 
            // using a URL on the class resource.
            // 
            URL lclassResource = RootLogger.class.getResource ( "/"+getLog4jXML() );
            if ( ( lclassResource != null ) && ( log == null ) ) { 
                System.out.println ( SHORT_NAME + ".initInstance () classResource URL  [" + lclassResource.getFile () + "]" );
                DOMConfigurator.configure ( lclassResource );
                log = Logger.getRootLogger ();
            }

            // URL : Next, try and see if a default log4j XML file 
            // is on the CLASSPATH 
            // 
            URL ldefaultLog4j = RootLogger.class.getResource ( "/log4j.xml" );
            if ( ( ldefaultLog4j != null ) && ( log == null ) ) { 
                System.out.println ( SHORT_NAME + ".initInstance () default log4j XML [" + lclassResource.getFile () + "]" );
                DOMConfigurator.configure ( ldefaultLog4j );
                log = Logger.getRootLogger ();
            }

        }
        catch ( Exception lioXcp ) {
            // TODO Auto-generated catch block
            lioXcp.printStackTrace();
        }

        if ( log == null ) { 
            ConsoleAppender console = new ConsoleAppender(); //create appender
            //configure the appender
            String PATTERN = "%d [%p|%c|%C{1}] %m%n";
            console.setLayout(new PatternLayout(PATTERN)); 
            console.setThreshold(Level.DEBUG);
            console.activateOptions();
            //add appender to any Logger (here is root)
            Logger.getRootLogger().addAppender(console);

            FileAppender fa = new FileAppender();
            fa.setName("DefaultLogger");
            fa.setFile("jbjf-default.log");
            fa.setLayout(new PatternLayout("%d %-5p [%c{1}] %m%n"));
            fa.setThreshold(Level.DEBUG);
            fa.setAppend(true);
            fa.activateOptions();

            //add appender to any Logger (here is root)
            Logger.getRootLogger().addAppender(fa);
            log = Logger.getRootLogger ();

        }

        getLog ().debug ( SHORT_NAME + ".setLog() - COMPLETE! [" + getLog ().getName () + "]" );

    }

    /**
     * <p>
     * Traditional getter method that returns the current log4j XML
     * directory path and filename.ext.
     * </p>
     * @return  The current log4j XML directory path and filename.ext
     */
    public static   String getLog4jXML() {
        return log4jXML;
    }

    /**
     * <p>
     * Traditional setter method that accepts a new log4j XML file.  
     * Call the initInstance ( getLog4jXML() ) to re-configure the 
     * log4j framework using the new file.
     * </p>
     * @param theLog4jXML   A new log4j XML file.
     */
    public static void setLog4jXML(String theLog4jXML) {
        log4jXML = theLog4jXML;
        // INFINITE LOOP : Causes an infinite loop...
        //initInstance ( theLog4jXML );
    }

}
\$\endgroup\$
  • 1
    \$\begingroup\$ "I want the following features available : ..." Are these implemented and working iwith your code? \$\endgroup\$ – πάντα ῥεῖ Jan 22 '17 at 17:09
  • 1
    \$\begingroup\$ How are you using this class in your code? \$\endgroup\$ – Simon Forsberg Jan 22 '17 at 17:38
  • \$\begingroup\$ Yes, the features are in the code at this point. I'm currently writing JUnit tests to strengthen it. I'll be using the class mostly with command line batch processing, but it will be used in Web/UI if needed. \$\endgroup\$ – lincolnadym Jan 22 '17 at 18:33
2
\$\begingroup\$

You mix up the singelton object pattern as defined by the "Gang of Four" in the book "Design patterns" with the Java singelton pattern.

The Java singelton pattern is the attempt to force singulatity of an object of a certain class programmatically, but this usually brings more problems then it solves.

The first an (IMHO) most important problem is: it is hard to mock (but not impossible, I know...) and therefore it is hard to verify that my code correctly communicates with your code in a unit test.

Then: it is not expendable. What if I want to add my on JSon-based configuration, or read the config from a database?

So when you continue to write your alternative to slf4j try to make the lives of your users easier, not harder.

\$\endgroup\$
  • \$\begingroup\$ I'm not so sure that the java singleton pattern is special compared to implementations in other programming languages. The Singleton Patern almost always causes more problems than it solves usually. \$\endgroup\$ – πάντα ῥεῖ Jan 22 '17 at 17:53
  • \$\begingroup\$ "I'm not so sure that the java singleton pattern is special compared to implementations in other programming languages." most likely not. \$\endgroup\$ – Timothy Truckle Jan 22 '17 at 17:59
  • \$\begingroup\$ Yes, I have only written a handful of singleton classes. This one I modeled after the JavaWorld article [javaworld.com/article/2073352/core-java/simply-singleton.html] \$\endgroup\$ – lincolnadym Jan 22 '17 at 18:42
  • \$\begingroup\$ I've been reading some posts in Stack Overflow...is it better if I do the singleton as discussed in this post, specifically the solution by Rodney Gitzel ? stackoverflow.com/questions/8256989/singleton-and-unit-testing \$\endgroup\$ – lincolnadym Jan 22 '17 at 19:04
  • \$\begingroup\$ @lincolnadym You probably shouldn't use Singletons beyond Factory Pattern classes. \$\endgroup\$ – πάντα ῥεῖ Jan 22 '17 at 19:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.