Logging in NetBeans

The logging system in NetBeans is based on the standard JDK's java.util.logging and complies to it as much as possible. This document sumarizes the basic usecases and shall be treated as a guide for writing good NetBeans ready logging code. The info given here is valid for default configuration of the logger as it is used in NetBeans. However it is possible to fully replace the system by providing own logging properties as in any other JDK application. Then of course the behaviour may get completely different.

Rather than printing raw exceptions to the console or implementing custom debug or logging facililities, code may use the Logger to access logging in a higher-level fashion. This way the logging messages can be dynamically turned on and off by single switch on command line or even during runtime.

Another important thing is to chain stack traces to exceptions using Throwable.initCause(Throwable), permitting you to throw an exception of a type permitted by your API signature while safely encapsulating the root cause of the problem (in terms of other nested exceptions). Code should use Logger.log(Level.SEVERE, msg, exception) rather than directly printing caught exceptions, to make sure nested annotations are not lost and to allow pluged in handlers of logging to process the exceptions.

It is possible to use global logger but it is preferred to create named and shared instances of loggers. The later has the advantage of finer control of what is going to be logged or not, as each named instance can be turned on/off individually by using a command line property.

Turning Logging On and Off

As the logging system is completely JDK based, one can use the traditional properties of LogManager and customize logging completely by themselves.

However there is a simpler way how to enable logging of an named logger. Just start NetBeans with -J-Dname.of.the.Logger.level=100 or any other number and all the log Levels with higher or equal value will immediatelly be enabled and handled by the system.

It is possible to turn the logging dynamically when the application is running. It is enough to just:

System.setProperty("name.of.the.Logger.level", "100");
LogManager.getLogManager().readConfiguration();
and logging state for the name.of.the.Logger is changed. The first line in the above code snippet changes the global properties and the second one asks the system to refresh the configuration of all loggers in the system.

Of course this only works if the default NetBeans logger is in place. Sometimes however it may make sence to provide completely different logger. This can be done by one of two JDK standard properties: java.util.logging.config.file or java.util.logging.config.class as described at LogManager's javadoc. If these properties are provide during the startup of the system, then the logging is fully dedicated to the configured custom loggers and of course no NetBeans standard configuration properties work.

How to Write Logging Friendly Code

Handling Exceptions

To handle an exception and sent it to the log file (and possibly show blinking icon to the user in bottom right corner of main window):

private static final Logger logger = Logger.getLogger(ThisClass.class.getName());
try {
    foo.doSomething();
} catch (IOException ioe) {
    logger.log(Level.SEVERE, null, ioe);
}

WARNING behaves the same way by default.

If the exception is not important, and by default shall not be shown or logged at all one can use the Level.FINE, Level.FINER or Level.FINEST:

try {
    foo.doSomething();
} catch (IOException ioe) {
    logger.log(Level.FINE, "msg", ioe);
}

The easiest way to make sure an exception is reported to the user is to use the dialog API with a code like this:

try {
  // some operations
} catch (Exception ex) {
  NotifyDescriptor.Exception e = new NotifyDescriptor.Exception(ex);
  DialogDisplayer.getDefault().notifyLater(e);
}

This code will present a dialog box with warning message extracted from the exception ex sometime in the future - e.g. when the AWT event queue is empty and can show the dialog. Use of notifyLater to plain notify is recommended in order to prevent deadlocks and starvations

Retain nested stacktraces / change exception type

To rethrow an exception use standard JDK's Throwable.initCause(Throwable) method. It is going to be properly annotated and printed when sent to logger:

public void doSomething() throws IOException {
    try {
        doSomethingElse();
    } catch (IllegalArgumentException iae) {
        IOException ioe = new IOException("did not work: " + iae);
        ioe.initCause(iae);
        throw ioe;
    }
}
// ...
try {
    foo.doSomething();
} catch (IOException ioe) {
    logger.log(Level.WARNING, null, ioe);
}
Logging a warning and debug messages

Logging shall usually be done with a named loggers, as that allows proper turning on and off from the command line. To log something into the log file one should use Level.INFO or higher:

private static final Logger LOG =
    Logger.getLogger("org.netbeans.modules.foo");
public void doSomething(String arg) {
    if (arg.length() == 0) {
        LOG.warning("doSomething called on empty string");
        return;
    }
    // ...
}

For writing debugging messages it is also better to have a named logger, but the important difference is to use Level.FINE and lower severity levels:

package org.netbeans.modules.foo;
class FooModule {
    public static final Logger LOG =
        Logger.getLogger("org.netbeans.modules.foo");
}
// ...
class Something {
    public void doSomething(String arg) {
        FooModule.LOG.log(Level.FINER, "Called doSomething with arg {0}", arg);
    }
}

Annotate Exceptions

There is an easy way how to annotate exceptions with localized and non-localized messages in NetBeans. One can use Exceptions.attachMessage or Exceptions.attachLocalizedMessage . The non-localized messages are guaranteed to be printed when one does ex.printStackTrace(), to extract associated localized message one can use Exceptions.findLocalizedMessage .

Using Structured Logging for Communication

In spite of what one might think the JDK logging API is not just about sending textual messages to log files, but it can also be used as a communication channel between two pieces of the application that need to exchange structured data. What is even more interesting is that this kind of extended usage can coexist very well with the plain old good writing of messages to log files. This is all possible due to a very nice design of the single logging record - the LogRecord.

Well written structured logging shall use the localized message approach and thus assign to all its LogRecords a ResourceBundle and use just a key to the bundle as the actually logged message. This is a good idea anyway, as it speeds up logging, because if the message is not going to be needed, the final string is not concatenated at all. However this would not be very powerful logging, so another important thing is to provide parameters to the LogRecord via its setParameters method. This, in combination with the MessageFormat used when the final logger is composing the logged message, further delay's the concatenations of strings. Morevoer it allows the advanced communication described above - e.g. there can be another module consumming the message which can directly get access to live objects and processes them in any way.

Here is an example of the program that uses such structured logging:

public static void main(String[] args) {
    ResourceBundle rb = ResourceBundle.getBundle("your.package.Log");
    int sum = 0;
    for (int i = 0; i < 10; i++) {        
        LogRecord r = new LogRecord(Level.INFO, "MSG_Add");
        r.setResourceBundle(rb);
        r.setParameters(new Object[] { sum, i });
        Logger.global.log(r);
        sum += i;
    }        
    LogRecord r = new LogRecord(Level.INFO, "MSG_Result");
    r.setResourceBundle(rb);
    r.setParameters(new Object[] { sum });
    Logger.global.log(r);
}    

Of course the two keys has to be reasonably defined in the Log.properties bundle:

# {0} - current sum
# {1} - add
MSG_Add=Going to add {1} to {0}
# {0} - final sum
MSG_Result=The sum is {0}

When executed with logging on, this example is going to print the expected output with the right messages and well substituted values:

INFO: Going to add 0 to 0
INFO: Going to add 1 to 0
INFO: Going to add 2 to 1
INFO: Going to add 3 to 3
INFO: Going to add 4 to 6
INFO: Going to add 5 to 10
INFO: Going to add 6 to 15
INFO: Going to add 7 to 21
INFO: Going to add 8 to 28
INFO: Going to add 9 to 36
INFO: The sum is 45    

This not surprising behaviour, still however it is one of the most efficient because the text Going to add X to Y is not constructed by the code itself, but by the logger, and only if really needed. So the descrbied logging style is useful of its own, however the interesting part is that one can now write following code and intercept behaviour of one independent part of code from another one:

public class Test extends Handler {
    private int add;
    private int sum;
    private int allAdd;
    public void publish(LogRecord record) {
        if ("MSG_Add".equals(record.getMessage())) {
            add++;
            allAdd += ((Integer)record.getParameters()[1]).intValue();
        }
        if ("MSG_Result".equals(record.getMessage())) {
            sum++;
        }
    }
    public void flush() {
        Logger.global.info("There was " + add + " of adds and " + sum + " of sum outputs, all adding: " + allAdd);
    }
    public void close() { flush(); }       
    static {
        Logger.global.addHandler(new Test());
    }
}   

The basic trick is to register own Handler and thus get access to provided LogRecords and process them in any custom way, possibly pretty different than just printing the strings to log files. Of course, this is only possible because the handler understand the generic names of logged messages - e.g. MSG_Add and MSG_Result and knows the format of their arguments, it can do the analysis, and output:

INFO: There was 10 of adds and 1 of sum outputs, all adding: 45

Indeed a structural logging can achive much more than shown in this simplistic example. Moreover it seems to be one of the most effective ways for logging, so it is highly recommended to use it where possible.