27

Salut to all Java gurus!

Since Java8 we can have default implementations in interfaces (yay!). However problem arises when you want to log from default method.

I have a feeling that it is not wise to call .getLogger() every time I want to log something in a default method.

Yes, one can define static variable in an interface - but that is not a good practice for interfaces anyway + it exposes the logger (must be public).

Solution I have at the moment:

interface WithTimeout<Action> {

    default void onTimeout(Action timedOutAction) {
        LogHolder.LOGGER.info("Action {} time out ignored.", timedOutAction);
    }

    static final class LogHolder {
        private static final Logger LOGGER = getLogger(WithTimeout.class);
    }
}

LogHolder is still visible to everybody which doesn't really make any sense since it does not provide any methods and it should be internal to the interface.

Does any of you know about better solution? :)

EDIT: I use SLF4J backed by Logback

Tomas Zaoral
  • 499
  • 2
  • 5
  • 11
  • "I have a feeling that it is not wise to call .getLogger() every time I want to log something in a default method." Have you performed performance measurements in order to back up this feeling? If not, don't optimize for performance if you don't need to. – Ray Feb 25 '15 at 13:58
  • 1
    Exposing a public `LogHolder` class or a public `Logger LOGGER` does not make much difference in my opinion. – assylias Feb 25 '15 at 14:00
  • 1
    I do not see any reason to _yay!_ – guido Feb 25 '15 at 14:02
  • I don't understand how this helps if multiple different classes are using this logger. – Tomáš Zato Feb 25 '15 at 15:33
  • @Ray I read something about .getLogger before I turned this option down. And calling .getLogger every time is considered anti-pattern: http://www.slf4j.org/faq.html#declared_static I haven't done any measurements of performance impact - but I would love to use the library in a fashion that is recommended by its creator. – Tomas Zaoral Feb 26 '15 at 15:53
  • @TomášZato Well, the point is to use this logger only in the interface itself, not in its implementations - they will use their own loggers. – Tomas Zaoral Feb 26 '15 at 15:55
  • @guido that's absolutely fine, have a beautiful day – Tomas Zaoral Feb 26 '15 at 15:57
  • @assylias well it does, since LogHolder will not expose the logger itself, but static Logger LOGGER in the interface will – Tomas Zaoral Feb 26 '15 at 15:58
  • @TomasZaoral all you need to do is make a private constructor on LogHolder, and not make it a static class. Then all the contents, even state, can stay private. As the Interface, you can access the methods and fields still. See my sample answer below. – The Coordinator Oct 31 '16 at 10:30

2 Answers2

22

Starting with JDK 16, you can hide the helper class inside a method:

interface WithTimeout<Action> {

    default void onTimeout(Action timedOutAction) {
        logger().info("Action {} time out ignored.", timedOutAction);
    }

    private static Logger logger() {
        final class LogHolder {
            private static final Logger LOGGER = getLogger(WithTimeout.class);
        }
        return LogHolder.LOGGER;
    }
}

Since JDK 9, interfaces are allowed to have private methods. To utilize this for a lazy initialization, we need to be able to declare a static field in a local class, which is allowed since JDK 16.

For older Java versions, if you don’t want to expose the class LogHolder to the public, don’t make it a member class of the interface. There is almost no benefit in making it a member class, you don’t even save typing as you have to qualify the field access with the name of the holder class anyway, regardless of whether it is a member class or a class within the same package:

public interface WithTimeout<Action> {

    default void onTimeout(Action timedOutAction) {
        LogHolder.LOGGER.info("Action {} time out ignored.", timedOutAction);
    }
}
final class LogHolder { // not public
    static final Logger LOGGER = getLogger(WithTimeout.class);
}

The disadvantage is the visibility within the same package. Of course, there can be only one top level class named LogHolder within a package.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    Yeah, but this does not restrict other members of the same package from accessing the logger itself. Maybe I'm being a purist too much. Decent programmer would hardly use logger of a different class nor would let it pass a code review I guess. – Tomas Zaoral Feb 26 '15 at 16:03
  • 1
    Since there is no way to have `private` members for `interface`s, there’s no way around it. But even if there was a way to make `LogHolder` a `private` class, that didn’t prevent other classes from calling `getLogger(WithTimeout.class)` themselves to get the same logger instance, so it’s not worth the effort. It’s useful to move the class out of the `interface` to avoid polluting the API with implementation artifacts but loggers shouldn’t be security relevant. – Holger Feb 26 '15 at 17:23
  • You could go one step farther and make Logger private. Then, even if someone can access LogHolder from the package scope, they won't be able to access that logger since it is private. But your interface can use it all the same. – The Coordinator Oct 29 '16 at 13:00
  • 1
    @Saint Hill: this only works, if `LogHolder` is a nested class, but then, it’ll be implicitly `public`, which is what this question is all about. You can only have either, a `private` `LOGGER` field or a non-`public` `LogHolder` class. – Holger Oct 31 '16 at 08:44
  • @Holger True enough. But the class can be publically visible and just not allow anyone else to use it. Anyone will always be able to see that some mystery is contained there-in. See my answer below. Now tell me that does not do what the OP requires :) I don't suggest this, but as a LogHolder class, it is quite useful. I'm not sure it has any other use than say top-secret recording of the calls that made it to the default methods (*evil laugh*) – The Coordinator Oct 31 '16 at 10:53
  • As the LogHolder class is visible to the whole package, I would name it more specific: `WithTimeoutLogHolder`. – Arend v. Reinersdorff Jan 09 '20 at 12:09
  • 1
    @Arendv.Reinersdorff then you have to redundantly write `WithinTimeout…` within the `WithTimeout` class. However, it would be a reasonable trace-off. – Holger Jan 09 '20 at 13:24
  • 1
    Updated, as JDK 16+ enable a real solution. – Holger May 09 '22 at 09:32
  • I am wondering if this line: private static final Logger LOGGER = getLogger(WithTimeout.class); should have LogHolder as the class name like this: private static final Logger LOGGER = getLogger(LogHolder.class); – Arsalan Siddiqui Jul 13 '23 at 17:22
  • 1
    @ArsalanSiddiqui the purpose of the logger is to behave as-if being inside the interface, as the logging calls are inside the interface’s default methods. So `WithTimeout.class` is the right class to specify. – Holger Jul 14 '23 at 06:52
2

Here you go.

The Logger is private to interface. No one other than this interface and its default methods can access anything inside Test2. And nothing can extend Test2 class.

No one is suggesting you do this ... but it works! And it is a great way to get access to a class logger for the main interface, and possibly a clever way to do something else not entirely crazy.

This is really the same as the LogHolder in the OP question, except that the Test2 class is all private methods and constructor private too, and class is not marked static.

And as an added bonus, it keeps state, statically and per-instance. (Don't do that in a real program, please!)

public class TestRunner {
    public static void main(String[] args) {
        Test test = new Test() {
        };
        test.sayHello("Jane");
        System.out.println("Again");
        test.sayHello("Bob");
    }
}
public interface Test {
    default void sayHello(String name) {
        Logger log = Test2.log;
        Test2 ref = Test2.getMine.apply(this);
        int times = ref.getTimes();
        for (int i = 0; i < times; i++) {
            System.out.println(i + ": Hello " + name);
        }
        log.info("just said hello {} times :)",times);
    }
    final class Test2 {
        private static final Logger log = LoggerFactory.getLogger(Test.class);
        private static final Map lookup = new WeakHashMap();
        private static final Function getMine = (obj) -> {
            return lookup.computeIfAbsent(obj, (it) -> new Test2());
        };
        private int calls = 0;
        private Test2() {
        }
        private void setCalls(int calls) {
            this.calls = calls;
        }
        private int getCalls() {return calls;}
        private int getTimes() {return ++calls;}
    }
}
The Coordinator
  • 13,007
  • 11
  • 44
  • 73
  • 3
    You basically changed the solution, the OP already had when opening this question, by adding a lot of stuff, the OP didn’t ask for. The class `Test2` still has `public` visibility and it is still `static` whether you declare it explicitly or not. Adding a `private` constructor is a small improvement, but doesn’t change the fact that the class as a whole is visible in the API of the `Test` interface. There is no reason to bring the discouraged instance to data mapping into it, as the OP never asked for associating data to instances. Was this really worth excavating a >1½ year old question? – Holger Oct 31 '16 at 11:13
  • 1
    I am just demonstrating what *can* be done, whether you like it or not. Other than the visibility of the mystery LogHolder helper class, there is nothing wrong with the solution. I think a lot of people will appreciate seeing the choices they have. The need to have a logger in a default method is real and this (and the OP's) provides a way to do it. This just makes sure that LogHolder is not extendable or instantiatable. – The Coordinator Oct 31 '16 at 17:01