How to initialize Loggers correctly?
Asked Answered
U

1

8

I have the following issue: I want to use the java.util.logging.Logger. No I found different resources 1,2,3, how one can modify the behavior of the logger.

Especially in the question of 2 a (in my opinion) good structure is given to initialize the loggers according to the class name. This also allows to modify verbosity on a package based level for debugging if needed.

After a bit of digging into the problem I found out, that the global logger and the "empty" logger (with name "") are not the same. See also the example below. I just created a logger foo.Bar, which is anchored at the empty logger instead of the logger called foo. Only if I first create the logger bar, the logger bar.Baz is anchored to it correctly.

This makes the approach in this question mainly useless as one cannot assume the parent loggers to be created before. One has to parse the class name and create loggers as needed, as far as I see.

Am I correct that I have to add some static {...} code to initialize the loggers in a recursive way before the own logger can be initialized? Does this have any negative effect if multiple classes call the Logger.getLogger(String) method for the package loggers (resulting in multiple calls overall, e.g both bar.Baz and bar.FooBaz get the logger bar)?

import java.util.Enumeration;
import java.util.logging.LogManager;
import java.util.logging.Logger;
public class TestLogger
{
    public static void main(String[] args)
    {
        // Create the logger directly
        Logger.getLogger("foo.Bar");
        // Create the logger objects step-by-step
        Logger.getLogger("bar");
        Logger.getLogger("bar.Baz");
        // Put the available loggers to output
        Enumeration<String> e = LogManager.getLogManager().getLoggerNames();
        while(e.hasMoreElements())
        {
            String s = e.nextElement();
            Logger l = Logger.getLogger(s);
            String p = (l.getParent() == null ? "none" : "'" + l.getParent().getName() + "'");
            System.out.println("'" + s + "': " + p);
        }
    }
}

The output of the program is

'bar': ''
'global': ''
'foo.bar': ''
'bar.baz': 'bar'
'': none
Unpractical answered 1/2, 2016 at 12:23 Comment(1)
The java.utils.Loggers can be tricky. Although I don't fully understand what the actual question is here, a (probably) important side note: Loggers can be garbage collected. When you write Logger.getLogger("foo").setLevel(x) in your main method, and then obtain a logger with Logger.getLogger("foo") somewhere else, it may be a different instance! The logger that you obtained in the main may already have been garbage collected, and the second getLogger("foo") call will then create a new instance.Kellam
A
1

One negative effect of having Logger.getLogger(String) in every class is that you won't be able to mock it (or pass a special logger) for tests.

Example: Let's say you need to test the following class C:

package a.b;
class C {
  private Logger logger;
  private int capacity;
  private int size;
  C(int capacity) {
    this.logger = Logger.getLogger("a.b.C");
    this.capacity = capacity;
    size = 0;
  }
  void add(int item) {
    if (size >= capacity) {
      logger.log("You already reached the capacity: " + capacity);
    }
    //...
  }
}

It is a requirement that add logs if the capacity is reached. Now you need to test it. It is very hard and hacky to make a test that tests this:

public void testCapacityReachedLogs() {
    C c = new C(2);
    c.add(1);
    c.add(2);
    // verify that c logged the exact string: "You already reached the capacity: 2"
}

However if you have:

package a.b;
class D {
  private Logger logger;
  private int capacity;
  private int size;
  D(Logger logger, int capacity) {
    this.logger = logger;
    this.capacity = capacity;
    size = 0;
  }
  void add(int item) {
    if (size >= capacity) {
      logger.log("You already reached the capacity: " + capacity);
    }
    //...
  }
}

Now you can mock Logger:

public void testCapacityReachedLogs() {
    Logger logger = getMock(Logger.class);
    D d = new D(logger, 2);
    d.add(1);
    d.add(2);
    verify(logger).log("You already reached the capacity: 2");
}

Note the "mock" related code doesn't necessarily follow the syntax of a framework, it's just for example.

Aliunde answered 1/2, 2016 at 12:34 Comment(3)
What do you mean by "mock it"? Can you please explain a bit?Unpractical
@ChristianWolf I added an example with "mock"Aliunde
Ahh, I see. Thanks for explaining.Unpractical

© 2022 - 2024 — McMap. All rights reserved.