Search code examples
debugginglogginglogbackslf4j

Defining parent or super-logger name with logback


I'm working on a big project with many submodules. When debugging component xyz, that component often accesses services in other modules. To log every debug message, we have to define many loggers in our logback.xml.

Is it possible to define overarching super loggers or parent loggers?

example: instead of writing this:

<logger name="com.a.b.c.xyz" level="debug" />
<logger name="com.a.b.d.core.xyz" level="debug" />
<logger name="com.a.b.e.xyz" level="debug" />
<logger name="com.a.b.e.f.xyz" level="debug" />
<logger name="com.a.b.t.services.xyz" level="debug" />

It is possible to define something like this:

<logger name="xyz-super" level="debug">
    <child-logger name="..." />
    <child-logger name="..." />
    ...
</logger>

Once debugging module xyz is done, everyone forgetts which packages were relevant for it, so keeping these parent loggers would help with future problems.


Solution

  • If I understand what you're asking for, you have a concept of a "component" which crosses Java packages, and you want to handle setting the logging level on the basis of which component it's in, and not necessarily on which package it's in. I see a few approaches one could take.

    1. While the standard for logger name is based on the class name (and thus the Java package that the class is in), you don't need to use that for your logger names. That is, you could have a logger hierarchy which is different from your package hierarchy. In your com.a.b.c.xyz class, you can get a logger with:

      final Logger logger = LoggerFactory.getLogger("com.a.b.xyz.c");
      

      While in your com.a.b.d.core.xyz class, get a logger with:

      final Logger logger = LoggerFactory.getLogger("com.a.b.xyz.d.core");
      

      And then you just can use normal logger level definitions, setting the logging level for com.a.b.xyz to get all the loggers underneath that component. It's a bit unconventional, and may confuse developers new to your project, but if you really want your logging hierarchy and your package hierarchy to be different, it's a thing you can do.

    2. Another approach is to leave your logging name hierarchy as is, but use SLF4J/Logback's Marker mechanism to "mark" each log message for each component.

      final Logger logger = LoggerFactory.getLogger(getClass());
      final Marker xyzComponent = MarkerFactory.getMarker("XYZ");
      …
      logger.info(xyzComponent, "A log message");
      

      You could then set up Filters in Logback based on the markers that you are looking for. It does require you to be consistent and make sure that every message that you care about is tagged with the appropriate Marker, but it's the closest thing to a "super logger" or "group logger" that the SLF4J architecture has. The Marker mechanism is really powerful and allows you to do just about anything with it, as long as your messages have the right Marker on them and you set up your logging configuration to filter to just the messages with the right filter.

    3. The other approach I can think of is to basically keep on doing what you're doing now, and specifying a lot of separate loggers at debug level, but have this "debug" logging configuration settings for each component in separate files. Then, when you need to debug a component, you just need to add (or uncomment?) the appropriate include element in your main logging settings.

      In file xyz-debug.xml:

      <included>
          <logger name="com.a.b.c.xyz" level="debug" />
          <logger name="com.a.b.d.core.xyz" level="debug" />
          <logger name="com.a.b.e.xyz" level="debug" />
          <logger name="com.a.b.e.f.xyz" level="debug" />
          <logger name="com.a.b.t.services.xyz" level="debug" />
      </included>
      

      In file abc-debug.xml:

      <included>
          <logger name="com.a.b.c.abc" level="debug" />
          <logger name="com.a.b.d.core.abc" level="debug" />
          <logger name="com.a.b.e.abc" level="debug" />
          <logger name="com.a.b.e.f.abc" level="debug" />
          <logger name="com.a.b.t.services.abc" level="debug" />
      </included>
      

      And then in your main logback.xml:

      <!--<include file="xyz-debug.xml"/>-->
      <!--<include file="abc-debug.xml"/>-->
      

      And you just uncomment the appropriate line when you need to debug that component. Perhaps a little fiddly and simplistic, and may be really confusing if somebody forgets to update the xyz-debug.xml when the xyz component is part of a new package, but I can imagine this working well enough for some teams. It also doesn't require any code changes, which may be a plus.

    Logback and SLF4J have a lot of power and possibilities, which is (as usual) both a strength and a weakness as it can take a while to learn about everything that they can do. But usually one can find a way to get them to work the way one wants, and sometimes one can find a way even better than what one had in mind.