Sunday, October 5, 2008

Auto Injection of a Logger into Spring beans

Dependency injection using Spring provides an easy way of injecting logging implementations to beans configured using Spring. Spring inherently use (JCL) Jakarta Commons Logging which may cause class loading issues.
Refer to this post http://www.qos.ch/logging/classloader.jsp .
After searching for a while I found out that we can replace JCL with SLF4j by just replacing the commons-logging.jar with the SLF4j provided jcl-over-slf4j.x.x.x.jar implementation (Please add the required dependencies). SLF4j provides a lot of good features over good old logging implementation.
Please refer to http://www.slf4j.org.
Not going into further details lets start with our implementation of
Logger. The first step is to create a Logger annotation. The Logger annotation is shown below.

/**
* Indicates Logger of appropriate type to
* be supplied at runtime to the annotated field.
*
* The injected logger is an appropriate implementation
* of org.slf4j.Logger.
*/
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(FIELD)
@Documented
public @interface Logger {
}
Now lets define a class that actually does the job of injecting the logger implementation.
/**
 * Auto injects the underlying implementation of logger into the bean with field
 * having annotation Logger.
 * 
 */
import java.lang.reflect.Field;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;

import static org.springframework.util.ReflectionUtils.FieldCallback;

public class LoggerInjector implements BeanPostProcessor {

 public Object postProcessAfterInitialization(Object bean, String beanName)
   throws BeansException {
  return bean;
 }

 public Object postProcessBeforeInitialization(final Object bean,
   String beanName) throws BeansException {
  ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
   public void doWith(Field field) throws IllegalArgumentException,
     IllegalAccessException {
    // make the field accessible if defined private
    ReflectionUtils.makeAccessible(field);
    if (field.getAnnotation(Logger.class) != null) {
     Log log = LogFactory.getLog(bean.getClass());
     field.set(bean, log);
    }
   }
  });
  return bean;
 }
}
Using it is even simpler. Just add the Logger annotation created above to the Log field in the required class. The class DemoBean below shows a field annotated this way. The import class Log is actually present in the jcl-over-slf4j.x.x.x.jar and does not depend on the JCL anymore.
import org.apache.commons.logging.Log;

public class DemoBean {

 @Logger
 private Log log;

 public void doSomething() {
  log.info("message");
  log.error("Lets see how the error message looks...");
 }
}
Just define the above bean in your spring application context defition. (I used XML configuration). Also you need to define a bean using the class LoggerInjector. Application contexts can auto-detect BeanPostProcessor beans in their bean definitions and apply them before any other beans get created.

14 comments:

  1. This works great. However logger can't be used in the constructor (logger is null). How can it be modified to work also in constructors?

    ReplyDelete
  2. Im wondering about the same thing. I have som logging I want to perform in some of my constructors

    ReplyDelete
  3. Did anyone figure out an annotation injection approach which would work in constructors or even static blocks? I notice that lipidlog has a java agent classloader approach but I would prefer a spring injection approach if possible.
    Simon

    ReplyDelete
  4. Good article.....

    To implement Logger using Spring AOP with simple steps ...you can find here
    http://velmurugan-pousel.blogspot.com/2010/11/logger-example-using-spring-aop.html

    ReplyDelete
  5. Spring dependency injection works on normal jvm, so Spring creates object and then inject dependencies throught properties. The only method to use logger in constructor is pass it throught constructor-args.
    You can try to make proxies using AspectJ/Spring AOP and then inject logger into constructor of real object.
    IMHO, it not neccessary.

    ReplyDelete
  6. @Vels: Your example gives a picture of how to use Aspects for logging. This is a good approach only if logging is required during the AOP supported methods e.g. Before, After, etc. It cannot be used to log some information within the method body as such.

    ReplyDelete
  7. Mein Gott you have simplified my logging life sir. One of those snippets that I can easily understand how it's done, but would not have thought to do it myself.

    ReplyDelete
  8. I did everything you wrote here. For a few classes the logger was injected but then there are few classes with logger = null. In a console output I got this (shortened):

    Bean 'XYZ' is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying).

    I think this is cause of my NPEs, because logger was never processed by Injector class. Do anybody know anything that could help? I am grateful for every advice. Thank you.

    ReplyDelete
  9. If you want to have a static field intialized you can do that outside of spring. Create a class loader, that uses the original classloader to load the class and after that fills in the static fields.

    ReplyDelete
  10. I mean: if you want to initialize a static field before the first instance is created. The above example in the blog can be modified to work for static fields as well. It will initialize the static logger and is available in the constructor, except when the constructor runs the first time.

    ReplyDelete
  11. Maybe it could be interesting for somebody: https://github.com/vbauer/herald

    ReplyDelete
  12. This is a very old post and I would simply recommend using Lombok and annotate you class with @Slf4j and one of logger annotations you want to use.

    ReplyDelete