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?

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


Java Logging Framework (part of the JDK),
Apache Log4J (bit old, but still going strong and actively maintained),
LogBack (created to provide a more modern approach than Log4J, by one of the creators of Log4J).


Some Logging Facades


SLF4J (by the creator of LogBack, but has adapters for other frameworks),
Apache Commons Logging (but a bit dated now).


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:


the LogBack manual on configuration,
the Log4J 2 manual on configuration.


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.
