32

I am using spring with aspect-j annotation support to allow for an @Loggable annotation. This allows automatic logging on a class based on the configuration.

I am wondering if I can somehow use this annotation to expose an slf4j Logger variable into the class for direct use, so that I don't have to do something to the effect of:

Logger logger = LoggerFactory.getLogger(MyClass.class);

It would be nice if the above was implicitly available due to the annotation and I could just go about doing logger.debug("..."); without the declaration. I'm not sure if this is even possible.

mlo
  • 321
  • 1
  • 3
  • 4

6 Answers6

22

You can use the BeanPostProcessor interface, which is called by the ApplicationContext for all created beans, so you have the chance to fill the appropriate properties.

I created a simple implementation, which does that:

import java.lang.reflect.Field;
import java.util.List;

import net.vidageek.mirror.dsl.Mirror;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class LoggerPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        List<Field> fields = new Mirror().on(bean.getClass()).reflectAll().fields(); 
        for (Field field : fields) {
            if (Logger.class.isAssignableFrom(field.getType()) && new Mirror().on(field).reflect().annotation(InjectLogger.class) != null) {
                new Mirror().on(bean).set().field(field).withValue(LoggerFactory.getLogger(bean.getClass()));
            }
        }
        return bean;
    }

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

You don't have to do any complex registration step, since the ApplicationContext is capable of recognizing BeanPostProcessor instances and automatically register them.

The @InjectLogger annotation is:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface InjectLogger {
}

And then you can easily use the annotation:

public static @InjectLogger Logger LOGGER;

...

LOGGER.info("Testing message");

I used the Mirror library to find the annotated fields, but obviously you may perform a manual lookup in order to avoid this additional dependency.

It's actually a nice idea to avoid repeated code, and even small issues that come from copying and paste the Logger definitions from other classes, like when we forget to change the class parameter, which leads to wrong logs.

Redder
  • 1,398
  • 1
  • 11
  • 16
  • Very cool solution. If you want to group logging by package or a set of classes you could use this solution to move that configuration in XML. Also, a very nice complete answer with clear examples. – Pace Jun 15 '11 at 13:41
  • this is the bad ass solution. – Junchen Liu Jul 31 '14 at 13:20
  • 2
    Want to make little remark. Instead of introducing custom 'Log' annotation , you can use 'standard' 'Autowired' annotation with 'required='false', and search target field not only by annotation but also by 'Logger' type in 'BeanPostProcessor'. – nndru Mar 01 '15 at 21:27
14

You can't do it with an aspect, but can help you in a, in my opinion, elegant way. See @Log annotation.

Martin Schröder
  • 4,176
  • 7
  • 47
  • 81
sinuhepop
  • 20,010
  • 17
  • 72
  • 107
  • Wow - Lombok is very cool. Basically the same thing as the Groovy AST Transformations. It looks a bit bleeding edge at the moment but maybe as OpenJDK evolves this will be something that is standard. – sourcedelica Jun 16 '11 at 13:16
  • 2
    I agree, lombok should be standard java (+1) – surfealokesea Oct 25 '15 at 08:55
  • Lombok is really good, even for creating getter, setter, constructor or builder... it rules! – Henrique Mar 06 '16 at 16:19
  • This is one of the few instances where Lombok works flawlessly: reduce boilerplate while only changing the internal structure of the class. – Torben Apr 16 '18 at 08:11
5

I think the solution from @Redder is a great way of doing this. However, I didn't want to include the Mirror library so I wrote an implementation of LoggerPostProcessor that uses the Java reflect library instead. Here it is:

package com.example.spring.postProcessor;

import com.example.annotation.InjectLogger;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class LoggerPostProcessor implements BeanPostProcessor {

    private static Logger logger = LoggerFactory.getLogger(LoggerPostProcessor.class);

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        List<Field> fields = Arrays.asList(bean.getClass().getDeclaredFields());

        for (Field field : fields) {
            if (Logger.class.isAssignableFrom(field.getType()) && field.getAnnotation(InjectLogger.class) != null) {

                logger.debug("Attempting to inject a SLF4J logger on bean: " + bean.getClass());

                if (field != null && (field.getModifiers() & Modifier.STATIC) == 0) {
                    field.setAccessible(true);
                    try {
                        field.set(bean, LoggerFactory.getLogger(bean.getClass()));
                        logger.debug("Successfully injected a SLF4J logger on bean: " + bean.getClass());
                    } catch (IllegalArgumentException e) {
                        logger.warn("Could not inject logger for class: " + bean.getClass(), e);
                    } catch (IllegalAccessException e) {
                        logger.warn("Could not inject logger for class: " + bean.getClass(), e);
                    }
                }
            }
        }

        return bean;
    }

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}
FGreg
  • 14,110
  • 10
  • 68
  • 110
  • This works, but only on the top level concrete class. If you have an Abstract class underneath that you've extended that contains the log variable, it doesn't seem to reach it. I had to declare my log variable in the concrete class and @Override my getLog() method in the concrete class as well. – lincolnadym Oct 16 '16 at 14:12
5

I want to make some improvements to @Redder's solution.

  • First - we can omit introduction of new annotation @Log, instead we can use Spring's @Autowired annotation with 'required' flag set to 'false' to make Spring not to check that bean was injected or not (because, we will inject it later).
  • Second - use Spring's ReflectionUtils API that provides all needed methods for field discovering and manipulation, so we don't need additional external dependencies.

Here an example (in Java 8, but can be rewritten in Java 7/6/etc., also slf4j facade is used but it can be replaced with any other logger):

@Component
public class LoggerPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        Logger logger = getLogger(bean.getClass());
        doWithFields(bean.getClass(), field -> {

            makeAccessible(field);
            setField(field, bean, logger);

        }, field -> field.isAnnotationPresent(Autowired.class) && Logger.class.equals(field.getType()));

        return bean;
    }
    ...
}
...
//logger injection candidate
@Autowired(required = false)
private Logger log;
nndru
  • 2,057
  • 21
  • 16
2

Since I got this as the first result when trying to do the same thing in CDI (JSR 299: Context and Dependency Injection), this link shows the straightforward way to do this using CDI (and also an alternative using Spring):

Basically, you only need to inject:

class MyClass {
   @Inject private Log log;

And have a logger factory like so:

@Singleton
public class LoggerFactory implements Serializable {
    private static final long serialVersionUID = 1L;

    static final Log log = LogFactory.getLog(LoggerFactory.class);

   @Produces Log createLogger(InjectionPoint injectionPoint) {
    String name = injectionPoint.getMember().getDeclaringClass().getName(); 
    log.debug("creating Log instance for injecting into " + name); 
    return LogFactory.getLog(name);
    }   
}

I found that I needed to add transient to the injected log so that I did not get a passivating scope exception in my session scoped beans:

@Named()
@SessionScoped()
public class MyBean implements Serializable {
    private static final long serialVersionUID = 1L;

    @Inject
    private transient Log log;
AdrieanKhisbe
  • 3,899
  • 8
  • 37
  • 45
peater
  • 1,233
  • 15
  • 20
0

Herald provides a very simple BeanPostProcessor which does all the magic for you. You can annotate any field of Spring bean with a @Log annotation to let Herald inject suitable logger in this field.

Supported logging frameworks:

  • JavaTM 2 platform's core logging framework
  • Apache Commons Logging
  • Simple Logging Facade for Java (SLF4J)
  • SLF4J Extended logger
  • Logback
  • Apache Log4j
  • Apache Log4j 2
  • JBoss Logging
  • Syslog4j
  • Syslog4j fork from Graylog
  • Fluent Logger for Java

It is also possible to add other logging frameworks.

Github repo: https://github.com/vbauer/herald

Vladislav Bauer
  • 952
  • 8
  • 19
  • I tried this library in my spring boot application. I added the dependency in pom.xml I used the annotation on a field.But the field is null when I try to use. – Harish Reddy Jan 04 '17 at 08:07
  • @HarishReddy Could you please contact with me or create an issue on Github? It looks strange, I've used this library in different projects and everything was fine. Also, tests in the Hareld project check integration with Spring Boot – Vladislav Bauer Jan 04 '17 at 19:10
  • Sure.I will create a github repo with just the code to reproduce and i will raise an issue on github along with that repo url. – Harish Reddy Jan 05 '17 at 07:35
  • @HarishReddy I've just answered in issue: https://github.com/vbauer/herald/issues/2 There was no problem with Hareld – Vladislav Bauer Jan 07 '17 at 14:16