Tuesday, December 20, 2011

Custom SLF4J Logger adapter

The Simple Logging Facade for Java or (SLF4J) is a simple facade for various logging frameworks. It decouples the logging framework from the application. The deployer can choose which logging framework to use. For every popular logging framework, an adapter Jar file is available. Deploy this file together with the core SLF4J Jar file, and the logging automagically works.

In my current work situation, a financial application I developed has to be integrated into an existing server application. This server application uses its own logging framework. Luckily, I've been using SLF4J, so logging integration is just a matter of writing a custom SLF4J adapter and replace the current adapter (SLF4J-Log4J) with it.

Writing an adapter is very easy. You only need to create three files:

  1. Create an implementation of the logger interface: org.slf4j.Logger
  2. Create a factory for the new logger using this interface: org.slf4j.ILoggerFactory
  3. Create your own binder class, so SLF4J knows which factory to use to get loggers: org.slf4j.spi.LoggerFactoryBinder
It is important to create these files in the package: org.slf4j.impl, because SLF4J is using this to find the correct adapter.

Firstly, we create the class that implements the SLF4J Logger interface. The implementation of this interface is used by SLF4J to delegate the logging. It's an interface with a large number of methods, so let the IDE generate the empty methods for you. Next is to implement the bodies of the methods to do the actual logging. In my case, I call the custom logging framework.

package org.slf4j.impl;

import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.helpers.MessageFormatter;

public class MyLoggerAdapter implements Logger {
    // Bunch of inherited methods here. Let your IDE generate this.
    // Implement these methods to do your own logging.
}

Now, we create a factory implementation for the Logger class. This class is used to retrieve Logger objects used by the application. Make sure it implements the ILoggerFactory interface.

package org.slf4j.impl;

import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;

import java.util.HashMap;
import java.util.Map;


public class MyLoggerFactory implements ILoggerFactory {
    private Map<String, MyLoggerAdapter> loggerMap;

    public MyLoggerFactory() {
        loggerMap = new HashMap<String, MyLoggerAdapter>();
    }

    @Override
    public Logger getLogger(String name) {
        synchronized (loggerMap) {
            if (!loggerMap.containsKey(name)) {
                loggerMap.put(name, new MyLoggerAdapter(name));
            }

            return loggerMap.get(name);
        }
    }
}

Finally, we need a way to register or bind the logger factory to SLF4J. To do that, we have to customize the StaticLoggerBinder class. You can almost use the same code provided by other adapters. I shamelessly ripped my code from the Log4J adapter.

package org.slf4j.impl;


import org.slf4j.ILoggerFactory;
import org.slf4j.spi.LoggerFactoryBinder;


public class StaticLoggerBinder implements LoggerFactoryBinder {

    /**
     * The unique instance of this class.
     */
    private static final StaticLoggerBinder SINGLETON
        = new StaticLoggerBinder();

    /**
     * Return the singleton of this class.
     *
     * @return the StaticLoggerBinder singleton
     */
    public static final StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }


    /**
     * Declare the version of the SLF4J API this implementation is
     * compiled against. The value of this field is usually modified
     * with each release.
     */
    // To avoid constant folding by the compiler,
    // this field must *not* be final
    public static String REQUESTED_API_VERSION = "1.6";  // !final

    private static final String loggerFactoryClassStr
        = MyLoggerFactory.class.getName();

    /**
     * The ILoggerFactory instance returned by the
     * {@link #getLoggerFactory} method should always be the same
     * object.
     */
    private final ILoggerFactory loggerFactory;

    private StaticLoggerBinder() {
        loggerFactory = new MyLoggerFactory();
    }

    public ILoggerFactory getLoggerFactory() {
        return loggerFactory;
    }

    public String getLoggerFactoryClassStr() {
        return loggerFactoryClassStr;
    }
}

Compile and deploy this code together with the SLF4J jar, and logging will be handled by the our new adapter.

Links:

14 comments:

  1. Thanks, this was a useful post. I only want to do custom things with info and error and use the standard logger for debug, etc. Can I use the normal SLF4J factory within my custom code?

    ReplyDelete
  2. Hi Ging,
    I am impressed with your knowledge...
    I would like to try your custom logger adapter in my sample applications to learn the work.
    Adapting your custom log, how am i suppose to have my "log4j.properties" file and where should i specify..
    Your blog never mentioned any thing about the .properties file usage.

    It will be nice if you can add , and will be useful for dummies like me. :)

    Grt work
    Gunash

    ReplyDelete
    Replies
    1. Hey Gunash,

      You can put the properties file in your class path. It should work automatically.

      Delete
  3. This comment has been removed by the author.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. I tried, but I Failed..May be i am very poor..But will earn soon.
    It Will be nice if u can advice me with a sample code.
    Also, if u can share your adaptor class utilizing SL4J and Complete sequence diagram will be grt for me to understand your IDEA. Same way i will also try top write my own thought process..
    Also, if come up with any thought process, can i share them with U.. Will u be able to validate my ideas ?

    I am just trying to learn from Java Gigs like u and thr dynamic mind..

    Am very much intrested in ur postings and will be looking or more postings rom u.

    Kinldy keep me notified on any new topics that you are planning to post.

    Now, I will wait or your adaptor class , sequence diagram o this postings and properties file.


    Thanks..
    Gunash

    ReplyDelete
  6. Can you tell me what else will be there in MyLoggerAdapter apart from methods that i need to override?
    I mean,in MyLoggerFactory on line no.21 you are calling constructor of MyLoggerAdapter ,I want to know what everything you are putting there in constructor?

    ReplyDelete
    Replies
    1. That depends what you need in your specific implementation. You only need to follow the interface that is enforced. What you do exactly in the implementation is entirely up to you. I this example I only pass the name as an example. If you need anything else, you can add it as parameter in the constructor.

      Delete
    2. So how MyLoggerAdapter constr will look in your case?
      And how will I use this custom logger to log smthng in my code.Generally when we create Logger instance using LoggerFactory.getLogger(ABC.class),it takes class name as param and here it takes String as param.

      Delete
  7. I think there is some confusion about the purpose of this blog post. SLF4J is a facade and only provide an interface to a real logging framework of your choosing. You can choose Log4j or other real logging framework to do the logging on behalf of SL4J. A facade decouples the logging framework from the library or application. In this example I only demonstrated the use case where you want to write your very own framework that is SLF4J compatible. If you don't want that, it's better to use an existing logging framework underneath SLF4J.

    ReplyDelete
    Replies
    1. Yeah..there was a big confusion..now its clear..Thanks a lot!

      Delete
  8. This comment has been removed by the author.

    ReplyDelete