usl4j – Ultra Simple Logging for JAVA

Back in the late 90’ties when I started programming JAVA there were no standard way of logging in JAVA programs.

One usually just used System.out.println(), System.err.println() and Exception.printStacktrace(). Often applications would build homemade logging frameworks that would also log information about timestamp etc, possibly logging to files and whatnot.

Later lots of different logging frameworks came about, e.g. JLog, log4j, commons-logging just to name a few. Larger application frameworks would supply their own logging framework; sometimes just repackaged versions of commons-logging or log4j.

When jdk 1.4 (a.k.a J2SE) was released, it contained a standard logging framework, and as far as I remember, this was actually based on the IBM Aphaworks developed JLog framework.

I thought that people would now stop using other third-party logging frameworks and just use jdk-logging, but no, a lot of people were angry that the JLog framework was chosen (and not log4j) and people would continue to use log4j. Other would use commons-logging, giving a simpler API and a “lightweight” solution towards not actually deciding on which logging framework to use.

Even another logging framework would see the day, because some people didn’t agree with neither jdk-logging, log4j or commons-logging and would still have the possibility to actually use either as the logging implementation: slf4j (Simple Logging Facade for Java (SLF4J)).

Personally, when I’m writing JAVA applications, I try to use as few third-party libraries as possible and use whatever the platform provides me. This in most cases means I will use jdk-logging as logging framework.

But I must admit that the API is bloated and awkward to use and it annoys me seriously that I have to provide a String as logger-name when calling the Logger.getLogger() factory method, resulting in code similar to the following:

import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class JdkLogTest {
    private static final Logger log = Logger.getLogger(JdkLogTest.class.getName());
 
    /**
     * @param args
     */
    public static void main(String[] args) {
        log.log(Level.INFO, "test started {0}", new Date());
        try {
            throw new Exception("Testing exception");
        } catch (Exception e) {
            log.log(Level.SEVERE, "exception caught", e);
        }
    }
}

Just for the fun of it, I decided to implement my own logging facade on top of jdk-logging. It should be a minimal KISS implementation, with a simple and straightforward API and simpler factory methods. This facade is called Ultra Simple Logging for JAVA or just usl4j for short.

The implementation consists of one interface and 2 classes:

  • Log: the interface defining the simple logging API.
  • LogFactory: the factory actually providing the developer with Log implementations.
  • LogJdkImpl: a jdk-logging implementation of the Log API.

Those classes along with a small test driver is provided here for free usage. If you prefer another logging framework as the base infrastructure, implement your own LogXxxImpl and change the appropriate line in the LogFactory implementation given. That is KISS 🙂

The simple Log API (net.udby.usl4j.Log):

package net.udby.usl4j;
 
/**
 * The simplest logging API - kiss
 *
 * @author Jesper Udby
 */
public interface Log {
    void debug(String msg, Object... args);
    void debug(String msg, Throwable exc);
    boolean isDebugEnabled();
 
    void info(String msg, Object... args);
    void info(String msg, Throwable exc);
    boolean isInfoEnabled();
 
    void config(String msg, Object... args);
    void config(String msg, Throwable exc);
    boolean isConfigEnabled();
 
    void warn(String msg, Object... args);
    void warn(String msg, Throwable exc);
    boolean isWarnEnabled();
 
    void error(String msg, Object... args);
    void error(String msg, Throwable exc);
    boolean isErrorEnabled();
}

The simple Log-factory API (net.udby.usl4j.LogFactory):

package net.udby.usl4j;
 
/**
 * The simple LogFactory providing appropriate Log implementations
 *
 * @author Jesper Udby
 */
public class LogFactory {
    private LogFactory() {
    }
 
    /**
     * KISS factory method that determines logger name from the calling class using stacktrace
     *
     * @return Log implementation for the calling class
     */
    public static Log getLogger() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        if (stackTrace.length > 2) {
            return getLogger(stackTrace[2].getClassName());
        } else {
            return getLogger(LogFactory.class);
        }
    }
 
    /**
     * Factory method for creating Log implementation for the class given
     *
     * @param clazz
     *            class to get Log implementation for
     * @return Log implementation for the class argument given
     */
    public static Log getLogger(Class<?> clazz) {
        return getLoggerImpl(clazz.getName());
    }
 
    /**
     * Factory method for creating Log implementation for the name given
     *
     * @param className
     *            class-name to get Log implementation for
     * @return Log implementation for the className argument given
     */
    public static Log getLogger(String className) {
        return getLoggerImpl(className);
    }
 
    /**
     * Internal factory method - this is where you change if you do not like the default jdk-logging implementation
     *
     * @param name
     *            Name of logger
     * @return appropriate Log implementation for the name given
     */
    private static Log getLoggerImpl(String name) {
        return LogJdkImpl.create(name);
    }
}

The simple jdk-logging Log implementation (net.udby.usl4j.LogJdkImpl):

package net.udby.usl4j;
 
import java.util.logging.Level;
import java.util.logging.Logger;
 
/**
 * A jdk-1.4 logging implementation
 *
 * @author Jesper Udby
 */
class LogJdkImpl implements Log {
    /** Level-mapping for debug logging */
    static final Level DEBUG = Level.FINE;
    /** Level-mapping for info logging */
    static final Level INFO = Level.INFO;
    /** Level-mapping for config logging */
    static final Level CONFIG = Level.CONFIG;
    /** Level-mapping for warn logging */
    static final Level WARN = Level.WARNING;
    /** Level-mapping for error logging */
    static final Level ERROR = Level.SEVERE;
 
    final Logger logger;
 
    private LogJdkImpl(String name) {
        this.logger = Logger.getLogger(name);
    }
 
    static LogJdkImpl create(String name) {
        return new LogJdkImpl(name);
    }
 
    @Override
    public void debug(String msg, Object... args) {
        log(DEBUG, msg, args);
    }
 
    @Override
    public void debug(String msg, Throwable exc) {
        log(DEBUG, msg, exc);
    }
 
    @Override
    public boolean isDebugEnabled() {
        return logger.isLoggable(DEBUG);
    }
 
    @Override
    public void info(String msg, Object... args) {
        log(INFO, msg, args);
    }
 
    @Override
    public void info(String msg, Throwable exc) {
        log(INFO, msg, exc);
    }
 
    @Override
    public boolean isInfoEnabled() {
        return logger.isLoggable(INFO);
    }
 
    @Override
    public void config(String msg, Object... args) {
        log(CONFIG, msg, args);
    }
 
    @Override
    public void config(String msg, Throwable exc) {
        log(CONFIG, msg, exc);
    }
 
    @Override
    public boolean isConfigEnabled() {
        return logger.isLoggable(CONFIG);
    }
 
    @Override
    public void warn(String msg, Object... args) {
        log(WARN, msg, args);
    }
 
    @Override
    public void warn(String msg, Throwable exc) {
        log(WARN, msg, exc);
    }
 
    @Override
    public boolean isWarnEnabled() {
        return logger.isLoggable(WARN);
    }
 
    @Override
    public void error(String msg, Object... args) {
        log(ERROR, msg, args);
    }
 
    @Override
    public void error(String msg, Throwable exc) {
        log(ERROR, msg, exc);
    }
 
    @Override
    public boolean isErrorEnabled() {
        return logger.isLoggable(ERROR);
    }
 
    private void log(Level level, String msg, Object... args) {
        if (logger.isLoggable(level)) {
            StackTraceElement ste = Thread.currentThread().getStackTrace()[3];
            String methodName = ste.getMethodName() + (ste.getLineNumber() > 0 ? ":" + ste.getLineNumber() : "");
            logger.logp(level, ste.getClassName(), methodName, msg, args);
        }
    }
 
    private void log(Level level, String msg, Throwable exc) {
        if (logger.isLoggable(level)) {
            StackTraceElement ste = Thread.currentThread().getStackTrace()[3];
            String methodName = ste.getMethodName() + (ste.getLineNumber() > 0 ? ":" + ste.getLineNumber() : "");
            logger.logp(level, ste.getClassName(), methodName, msg, exc);
        }
    }
}

Last but not least, a simple sample code showing the beauty of the simplicity 🙂 Let me introduce the LogTest:

import java.util.Date;
 
import net.udby.usl4j.Log;
import net.udby.usl4j.LogFactory;
 
public class LogTest {
    private static final Log log = LogFactory.getLogger();
 
    /**
     * @param args
     */
    public static void main(String[] args) {
        log.info("test started {0}", new Date());
        try {
            throw new Exception("Testing exception");
        } catch (Exception e) {
            log.error("exception caught", e);
        }
    }
}

The source for usl4j can be downloaded here: Source for usl4j.

Update 2013-02-21: Slightly modified version now on GitHub: https://github.com/judby/usl4j

References

  1. JavaTM Logging Overview
  2. Apache log4j
  3. Apache commons logging (clogging)
  4. Simple Logging Facade for Java (SLF4J)

About Jesper Udby

I'm a freelance computer Geek living in Denmark with my wife and 3 kids. I've done professional software development since 1994 and JAVA development since 1998.
This entry was posted in Java, Open Source and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *