Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to initialize Loggers correctly?

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
like image 353
Christian Wolf Avatar asked Feb 01 '16 12:02

Christian Wolf


1 Answers

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.

like image 100
Gavriel Avatar answered Oct 01 '22 02:10

Gavriel