34

As my current Java projects grow bigger and bigger, I feel a likewise growing need to insert debug output in several points of my code.

To enable or disable this feature appropriately, depending on the opening or closure of the test sessions, I usually put a private static final boolean DEBUG = false at the beginning of the classes my tests are inspecting, and trivially use it this way (for example):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

  public void myMethod(String s) {
    if (DEBUG) {
      System.out.println(s);
    }
  }
}

and the like.

But that doesn't bliss me out, because of course it works but there could be too many classes in which to set DEBUG to true, if you are not staring at just a couple of them.

Conversely, I (like - I think - many others) wouldn't love to put the whole application in debug mode, as the amount of text being output could be overwhelming.

So, is there a correct way to architecturally handle such situation or the most correct way is to use the DEBUG class member?

4
  • 14
    in Java, the correct way is NOT to use homebrew code for logging. Pick an established framework, don't reinvent the wheel
    – gnat
    Nov 16, 2012 at 13:25
  • I use a boolean DEBUG in some of my more complicated classes, for the same reason as you stated. I don't usually want to debug the whole application, just the class giving me problems. The habit came from my COBOL days, where DISPLAY statements were the only form of debugging available. Nov 16, 2012 at 19:24
  • 1
    I would also recommend relying more on a debugger when possible and not littering your code with debug statements. Nov 17, 2012 at 12:04
  • 1
    Do you practice Test Driven Development (TDD) with unit tests? Once I started doing that, I noticed a massive reduction in 'debug code'.
    – JW01
    Nov 21, 2012 at 9:07

4 Answers 4

51

You want to look at a logging framework, and maybe at a logging facade framework.

There are multiple logging frameworks out there, often with overlapping functionalities, so much so that over time many evolved to rely on a common API, or have come to be used through a facade framework to abstract their use and allow them to be swapped in place if needed.

Frameworks

Some Logging Frameworks

Some Logging Facades

Usage

Basic Example

Most of these frameworks would allow you to write something of the form (here using slf4j-api and logback-core):

package chapters.introduction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// copied from: http://www.slf4j.org/manual.html
public class HelloWorld {

  public static void main(String[] args) {
    final Logger logger = LoggerFactory.getLogger(HelloWorld.class);

    logger.debug("Hello world, I'm a DEBUG level message");
    logger.info("Hello world, I'm an INFO level message");
    logger.warn("Hello world, I'm a WARNING level message");
    logger.error("Hello world, I'm an ERROR level message");
  }
}

Note the use of a the current class to create a dedicated logger, which would allow SLF4J/LogBack to format the output and indicate where the logging message came from.

As noted in the SLF4J manual, a typical usage pattern in a class is usually:

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  

public class MyClass {

    final Logger logger = LoggerFactory.getLogger(MyCLASS.class);

    public void doSomething() {
        // some code here
        logger.debug("this is useful");

        if (isSomeConditionTrue()) {
            logger.info("I entered by conditional block!");
        }
    }
}

But in fact, it's even more common to declare the logger with the form:

private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);

This allows the logger to be used from within static methods as well, and it is shared between all instances of the class. This is quite likely to be your preferred form. However, as noted by Brendan Long in comments, you want to be sure to understand the implications and decide accordingly (this applies to all logging frameworks following these idioms).

There are other ways of instantiating loggers, for instance by using a string parameter to create a named logger:

Logger logger = LoggerFactory.getLogger("MyModuleName");

Debug Levels

Debug levels vary from one framework to another, but the common ones are (in order of criticality, from benign to bat-shit bad, and from probably very common to hopefully very rare):

  • TRACE Very detailed information. Should be written to logs only. Used only to track the program's flow at checkpoints.

  • DEBUG Detailed information. Should be written to logs only.

  • INFO Notable runtime events. Should be immediately visible on a console, so use sparingly.

  • WARNING Runtime oddities and recoverable errors.

  • ERROR Other runtime errors or unexpected conditions.

  • FATAL Severe errors causing premature termination.

Blocks and Guards

Now, say you have a code section where you are about to write a number of debug statements. This could quickly impact your performance, both because of the impact of the logging itself and of the generation of any parameters you might be passing to the logging method.

To avoid this sort of issue, your often want to write something of the form:

if (LOGGER.isDebugEnabled()) {
   // lots of debug logging here, or even code that
   // is only used in a debugging context.
   LOGGER.debug(" result: " + heavyComputation());
}

If you hadn't used this guard before your block of debug statements, even though the messages may not be output (if, for instance, your logger is currently configured to print only things above the INFO level), the heavyComputation() method would still have been executed.

Configuration

Configuration is quite dependent on your logging framework, but they offer mostly the same techniques for this:

  • programmatic configuration (at runtime, via an API - allows for runtime changes),
  • static declarative configuration (at start-time, usually via an XML or properties file - likely to be what you need at first).

They also offer mostly the same capabilities:

  • configuration of the output message's format (timestamps, markers, etc...),
  • configuration of the output levels,
  • configuration of fine-grained filters (for instance to include/exclude packages or classes),
  • configuration of appenders to determine where to log (to console, to file, to a web-service...) and possibly what to do with older logs (for instance, with auto rolling files).

Here's a common example of a declarative configuration, using a logback.xml file.

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

As mentioned, this depends on your framework and there may be other alternatives (for instance, LogBack also allows for a Groovy script to be used). The XML configuration format may also vary from one implementation to another.

For more configuration examples, please refer (amongst others) to:

Some Historical Fun

Please note that Log4J is seeing a major update at the moment, transitioning from version 1.x to 2.x. You may want to have a look at both for more historical fun or confusion, and if you pick Log4J probably prefer to go with the 2.x version.

It's worth noting, as Mike Partridge mentioned in comments, that LogBack was created by a former Log4J team member. Which was created to address shortcomings of the Java Logging framework. And that the upcoming major Log4J 2.x version is itself now integrating a few features taken from LogBack.

Recommendation

Bottom line, stay decoupled as much as you can, play around with a few, and see what works best for you. In the end it's just a logging framework. Except if you have a very specific reason, apart from ease of use and personal preference, any of these would do rather OK so there's not point being to hung over it. Most of them can also be extended to your needs.

Still, if I had to pick a combination today, I'd go with LogBack + SLF4J. But if you had asked me a few years later I'd have recommended Log4J with Apache Commons Logging, so keep an eye over your dependencies and evolve with them.

9
  • 1
    SLF4J and LogBack were written by the guy who originally wrote Log4J. Nov 16, 2012 at 13:18
  • 4
    For those who might be worried about performance impacts of logging: slf4j.org/faq.html#logging_performance Nov 16, 2012 at 13:30
  • 2
    It's worth mentioning that it's not so clear-cut whether you should make your loggers static, since it saves a small amount of memory, but causes problems in certain instances: slf4j.org/faq.html#declared_static Nov 16, 2012 at 15:45
  • 1
    @MikePartridge: I am aware of the content of the link, but it still won't prevent parameter evaluation for instance. the reason byt parameterized logging is more performant is because the processing of the log message won't occur (string notably concatenation). However, any method call will get executed if it's passed as a parameter. So, depending on your use case, blocks can be useful. And as mentioned in the post, they can also be useful for you just to group other debug related activities (not just logging) to occur when the DEBUG level is enabled.
    – haylem
    Nov 16, 2012 at 23:38
  • 1
    @haylem - that's true, my mistake. Nov 19, 2012 at 13:21
2

use a logging framework

most of the time there's a static factory method

private static final Logger logger = Logger.create("classname");

then you can output your logging code with different levels:

logger.warning("error message");
logger.info("informational message");
logger.trace("detailed message");

then there will be a single file where you can define which messages for each class should be written to the log output (file or stderr)

1

This is exactly what logging frameworks like log4j or the newer slf4j are meant for. They allow you to control logging in great detail, and configure it even while the application is running.

0

A logging framework is definitely the way to go. However, you also must have a good test suite. Good test coverage may often eliminate the need for debug output all together.

2
  • If you use a logging framework and have debug logging available - there will come a time when this will save you from having a really bad day. Nov 17, 2012 at 15:59
  • 1
    I did not say you shouldn't have logging. I said you need to have tests first.
    – Dima
    Nov 17, 2012 at 18:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.