Logging and Exception Handling in NetBeans

The logging system in NetBeans is based on the standard JDK's java.util.logging, however it also adds few special NetBeans tweaks. This document sumarizes the basing usecases and shall be treated as a guide for writing good NetBeans ready logging and reporting 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 described in any other JDK application. Then of course the behaviour may get completely different.

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

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 or Level.CONFIG, msg, exception) rather than directly printing caught exceptions, to make sure nested annotations are not lost.

Also localized messages may be annotated to exceptions so that code which can deal with a caught exception with a user-visible UI can display a polite and helpful message. Messages with no localized annotation can be handled in a default way while the details are reserved for the log file.

It is possible to use anonymous loggers or create own named and shared instances . 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 value will immediatelly be enabled and handled by the system.

How to Write Logging Friendly Code

Handling Exceptions

To handle an exception if it might be an important error (e.g. show it to the user):

try {
    foo.doSomething();
} catch (IOException ioe) {
    Logger.getAnonymousLogger().log(Level.SEVERE, "msg", ioe);
}

If it is not very important but should be sent to the log file:

try {
    foo.doSomething();
} catch (IOException ioe) {
    Logger.getAnonymousLogger().log(Level.CONFIG, "msg", ioe);
}

If it is the normal outcome of a user action and there is no need to show stack traces to the user:

try {
    foo.doSomething();
} catch (IOException ioe) {
    Logger.getAnonymousLogger().log(Level.INFO, "msg", ioe);
}

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

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

You can also specify the severity when you are creating the exception (by annotating it), rather than relying on the notifier to do this. In that case, if the notifier just use the log (Level.ALL severity), the annotated severity is used.

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.getAnonymousLogger().log(Level.INFO, "msg", ioe);
}
Provide a user-visible (localized) message

Annotating an exception with a human readable message requires use of a specific NetBeans support class Exceptions which contains static methods that associate exceptions with a localized messages, severity levels and provides methods to also query such associated information:

public void doSomething(File f) throws IOException {
    if (!f.isFile()) {
        IOException e = new IOException("Not a file: " + f); // NOI18N
        // For what the user actually sees:
        Exceptions.annotate(e,
            NbBundle.getMessage(This.class, "EXC_not_a_file", f)
        );
        throw e;
    }
}

You can also add the message when the exception is caught rather than when it is thrown. You could even have one piece of code throw an exception, another annotate it, and yet another notify it.

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(Level.WARNING, "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) {
        if (LOG.isLoggable(Level.FINER)) {
            LOG.log("Called doSomething with arg " + arg);
        }
    }
}