Pass the logger

I call this the Logger design pattern :)

We are lucky to have a Logger API (slf4J) which allows us to choose implementation as we see fit, I specifically like loggback as it offers simple to advance configuration.

One trick I use when dealing with class abstraction and inheritance is logging using the concrete class and not using any parent logger.

I found two main ways to pass the logger to make sure I log using the right Logger instance.

Loggers are static

public abstract class Parent  {

    private static final Logger log = LoggerFactory.getLogger(Parent.class);

    protected void doSomething() {
        // will log using this class Logger
        log.info("Success");
    }
}

My typical log message will be:

04-07-2019T11:00:00.662 [main] INFO Parent.doSomething Success

We have some default methods we would like to use in our inherited classes. But now any inherited class will log using the Parent class logger.

public class Child extends Parent {

    private static final Logger log = LoggerFactory.getLogger(Child.class);

    protected void doSomethingElse() {
        // will log using this class Logger
        log.info("Some message");
        doSomething(); // will log using parent logger
    }

}

Now the log is coming from two different classes using two different Loggers

04-05-2019T11:11:00.662 [main] INFO Child.doSomethingElse Some Message
04-05-2019T11:11:00.662 [main] INFO Parent.doSomething Success

It will be useful to have both log messages with the same Child class message as we might have many derived classes and we need to know the origin caller of doSomething()

Constructor override

We can allow the derived classes to pass the Logger into the constructor:

public abstract class Parent  {

    private final Logger logger;

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

    protected void doSomething() {
        // will log using the injected Logger instance
        log.info("Success");
    }

}

Our Child class will have to pass the static instance of the Logger to our Parent class

public class Child extends Parent {

    private static final Logger log = LoggerFactory.getLogger(Child.class);

    public Child() {
        super(log);
    }

    protected void doSomethingElse() {
        // All logging will be done using this instance Logger
        log.info("Some message");
        doSomething();
    }

}

Log will now have the Child class name in the log.

04-05-2019T11:21:00.332 [main] INFO Child.doSomethingElse Some Message
04-05-2019T11:21:00.332 [main] INFO Child.doSomething Success

Method override

In this case we might want to mix the two loggers where some logging will be done using the parent logger and some will be done using our child class. We can pass the Logger instance to the parent method call.

public abstract class Parent  {

    protected void doSomething(Logger log) {
        // will log using the injected Logger instance
        log.info("Success");
    }

}

And in the child class we don't need the constructor anymore and we simply pass the Logger to the method.

public class Child extends Parent {

    private static final Logger log = LoggerFactory.getLogger(Child.class);

    protected void doSomethingElse() {
        // All logging will be done using this instance Logger
        log.info("Some message");
        doSomething(log); // we pass the logger to the method
    }

}

Log will now have the Child class name in the log.

04-05-2019 11:31:00.100 [main] INFO Child:doSomethingElse Some Message
04-05-2019 11:31:00.100 [main] INFO Child:doSomething Success

Mix and match

There are many ways to go about implementing these ideas with default Logger and Pass the logger so I was just outlining the edge cases. You can mix and match the two ideas in many different ways.

If you find other ideas useful please share in the comments.

Hope you find this short post helpful.

Thanks for reading.

25