15

This Mkyong tutorial suggests intialising loggers this way:

@Controller
public class WelcomeController {

    private static final Logger logger = Logger.getLogger(WelcomeController.class);

   // etc

}

Now presumably every other class you use, that has a logger will initialise their loggers the same way.

My question is - is this the best way to do it? It seems... repetitive.

  • 2
    What do you find verbose about this (setting aside Java's syntax)?. You have to create a variable to hold the logger, and you have to tell getLogger() the name of the logger to get. – kdgregory Jun 26 '15 at 11:08
  • 2
    @kdgregory the fact that i'm doing the same thing in every class. – dwjohnston Jun 26 '15 at 12:38
  • 1
    Take a look at this question on Stack Overflow – toniedzwiedz Jun 27 '15 at 14:39
  • 3
    Too old to migrate, but this question is off-topic here and more suitable for StackOverflow. – Andres F. Feb 26 '16 at 3:57
  • 1
    @AndresF. I think i put it here because it's more of a question about code style/design patterns, than a technical issue. – dwjohnston Feb 26 '16 at 4:26
16

Your comment says that "verbose" refers to the need to repeat this line of code in every class. My first response is that, in the big picture, adding two lines of code (variable definition plus import statement) to every class isn't that big a deal. Especially since you only need to add them to the classes that have behavior and therefore need to do logging. That said, the specific line of code that you're using is prone to copy-paste errors (more on that later).

But, since you want alternatives, here are a few, with reasons that you might or might not want to use them.

Use a single logger for the entire app

If you don't care about what class is reporting, or are willing to put all necessary context into the message, than a simple singleton logger will do the job:

LoggerSingleton.getInstance().debug("MyController is running")

In my opinion, one of the big benefits of a logging framework is having the context provided by separate logger instances -- if only to direct the log messages to different destinations. I wouldn't give that up just to save one line of code (you still need the import).

Plus, this increases verbosity at the point of use, which will end up in far more keystrokes.

Create your loggers at the point of use

I'm throwing this one out just because it eliminates the variable. I don't think I need to comment on it. Although it does show my preferred technique for getting a logger instance.

Logger.getLogger(getClass()).debug("blah blah blah");

Use a bean post-processor to inject the logger

Your example uses Spring, and Spring lets you hook into the bean initialization code. You could create a post-processor that inspects the bean for a logger member variable, and creates a Logger instance when it finds one.

While such a post-processor is only a few dozen lines of code, it's another moving part in your application, and therefore another potential source of bugs. I prefer to have as few of those as possible.

Use a mixin

Scala and Groovy provide traits, which let you encapsulate behavior. A typical Scala pattern is to create a Logging trait, then add it to the class that needs logging:

class MyController with Logging

Unfortunately, this means that you have to switch languages. Unless you're using Java 8, in which case you can create a Logging interface with a "default method":

public interface Logging {
    default Logger getLogger() {
        return Logger.getLogger(getClass());
    } 
}

Now, within your class code, you can simply use

getLogger().debug("blah blah blah");

While easy, this has a couple of disadvantages. For one thing, it pollutes the interface of every class that uses it, because all interface methods are public. Perhaps not that bad if you use it only for classes that are instantiated and injected by Spring, especially if you follow interface/implementation separation.

The bigger problem is that it has to look up the actual logger instance on every call. Which is fast, but unnecessary.

And you still need an import statement.

Move logger to a superclass

I'll repeat: I don't find repeated logger definitions verbose, but if you do I think this the best approach to eliminating them.

public abstract class AbstractController {
    protected Logger logger = Logger.getLogger(getClass());
}

Now your controller classes inherit from AbstractController, and they have access to the logger variable. Remember that you have to put the @Controller annotation on the concrete class.

Some people will find this a perversion of inheritance. I have tried to mollify them by naming the class AbstractController rather than AbstractProjectClass. You can decide for yourself whether or not there's an is-a relationship.

Other people will object to the use of an instance variable rather than a static variable. IMO static loggers are prone to copy-paste errors, because you have to explicitly reference the classname; getClass() ensures that your logger is always correct.

| improve this answer | |
  • I think you should be careful about getClass() in inheritance class, MethodHandles.lookup().lookupClass() is better after jdk 7 – yuxh Jul 15 '19 at 3:16
  • @luxh - do you have an explanation for that? – kdgregory Jul 16 '19 at 10:36
  • check here: stackoverflow.com/a/6653577/4652536 – yuxh Aug 1 '19 at 1:44
  • @yuxh - the only mention of MethodHandles in that question (which wasn't in the answer you linked), is a similar unsupported assertion with a comment asking for clarification. Do you have authoritative support for the assertion that MethodHandles.lookup().lookupClass() is better than Object.getClass()? – kdgregory Aug 1 '19 at 10:38
  • MethodHandles.lookup().lookupClass() can be used for static variables, is not prone to copy-paste errors and is fast: stackoverflow.com/a/47112323/898747 It does mean an extra inport, but i quite like my loggers bein static so it is at least worth a mention :) – FableBlaze Feb 19 at 11:49
1

To extend the answer provided by @kdgregory, Groovy provides @Slf4j(groovy.util.logging.Slf4j) out of the box as an annotation that performs an AST transformation on the class to tack it with a logger which assumes the variable name log by default if left unspecified.

| improve this answer | |

Not the answer you're looking for? Browse other questions tagged or ask your own question.