26

What I want is to make spring autowire a logger. So, in other words, I want to have this working:

import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MainController {

    @Autowired
    private Logger logger;
    
    @RequestMapping("/")
    public String enterSite(HttpServletResponse response) {
        logger.info("site entered");
        return "welcome";
    }
}

Right now it throws an exception at startup: "No qualifying bean of type [org.slf4j.Logger] found for dependency...".

My pom.xml dependencies:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.0.M1</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.1-901.jdbc4</version>
        </dependency>
        <!-- <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> 
            </dependency> -->
    </dependencies>

I read this: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-logging

It says if you use one of the starter poms (I do) Logback is used - but for internal logging. Can it be autowired in my classes?

Dherik
  • 17,757
  • 11
  • 115
  • 164
vic
  • 378
  • 1
  • 3
  • 10

5 Answers5

31

If the objective here is code reduction then try Project Lombok. You then don't even need to declare the logger - just add an annotation and use log instead of logger

So your above code will now look like this:

import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
// import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class MainController {

    @RequestMapping("/")
    public String enterSite(HttpServletResponse response) {
        log.info("site entered");
        return "welcome";
    }
}
Simon Jenkins
  • 688
  • 8
  • 11
  • 1
    How to inject logger object if we write a test case for this controller? @InjectMock doesn't seem to be working – Pruthviraj Nov 26 '18 at 12:37
  • There is no injection mechanism here. Project Lombok simply inserts the logger initiation code for you i.e.: `private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MainController.class);` I'm not sure why you would want to inject a logger. If you want to disable it or re-direct the output then this can be done in the logger's configuration file. Your test configuration will override the normal configuration so there'll be no conflicts with anything. – Simon Jenkins Nov 27 '18 at 13:19
  • This is not really answer the question. The question asks for a injectable logger solution and the answer explains how use static logger using Lombok annotation. – Dherik Mar 25 '19 at 12:47
  • 2
    "This is not really answer the question" [_sic_]. You are correct. Hence the caveat in my comment thus: _There is no injection mechanism here._ And my speculative assumption statement before my solution: _If the objective here is code reduction..._ I was trying to second guess the motivation behind the requirement and offer help. I agree it is not answering the question to the letter of the law. – Simon Jenkins Apr 01 '19 at 12:00
17

Although it is not the usual way you can add a logger bean directly in your context reproducing the classic bind:

private final Logger logger = LoggerFactory.getLogger(MainController.class);

simply inserting in the spring context:

<bean id="logger" scope="prototype" class="org.slf4j.LoggerFactory" factory-method="getLogger">
    <constructor-arg name="name" value="youLoggerName" />
</bean>

then you can simply inject your logger:

@Autowired
private Logger logger;
fl4l
  • 1,580
  • 4
  • 21
  • 27
  • 1
    If we create logger through bean, it will not available in actuator logger endpoints. So, I can not configure logger runtime. Is there any way so that it will be available in actuator logger endpoints? – Nitul Dec 05 '17 at 08:02
15

Solution using @Bean:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class LoggerConfiguration {

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Logger logger(InjectionPoint injectionPoint){
        return LoggerFactory.getLogger(injectionPoint.getMethodParameter().getContainingClass());
    }

}

After that, just inject the Logger using Constructor Injection (field injection will not work):

@Service
class SomeService {

    private Logger logger;

    public SomeService(Logger logger;) {
        this.logger = logger;
    }

    public void someMethod() {
        logger.error("Some log");
    }  
}
Dherik
  • 17,757
  • 11
  • 115
  • 164
  • 1
    Can you include the import statements in your code sample? This will remove any ambiguity for developers wishing to adopt this solution. – Simon Jenkins Apr 02 '19 at 13:13
  • @Hiru, see again. This strategy only works for constructor injection. My last answer was wrong about showing that will work with field injection. – Dherik Apr 05 '19 at 13:53
  • Note that the `InjectionPoint` can be constructed via field injection or parameter injection, so you should do your NULL checks to ensure you are returning the logger for the correct usage – smac89 Jun 26 '19 at 20:59
  • 2
    See this article for an example that handles both method parameter injection and field injection: https://medium.com/simars/inject-loggers-in-spring-java-or-kotlin-87162d02d068 – Colin D Bennett May 05 '20 at 20:48
6

You can have Spring autowire a Logger instance, but it would be a very unusual thing to do (you'd need a bean of type Logger to be in your application context). The far more usual approach is to initialise the logger where it's declared, configuring it with the class that will be using it for logging:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MainController {

    private final Logger logger = LoggerFactory.getLogger(MainController.class);

}
Andy Wilkinson
  • 108,729
  • 24
  • 257
  • 242
3

This is how I got it working for field injection

LoggingConfiguration.java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

    @Configuration
    public class LoggingConfiguration {

        @Bean
        public Logger log() {
            var log = LoggerFactory.getLogger("com.bitsmonkey.dummy");
            return log;
        }
    }

Now inside the controller

DummyController.java

@Controller
public class DummyController {

@Autowired
private Logger log;

//......
Arjun Shetty
  • 1,575
  • 1
  • 15
  • 36
  • When I try to use log in my class, I am getting an error - Cannot make a static reference to the non-static field log. – Niki May 11 '21 at 05:47