Code documentation

Development tools

Code Structure

Techniques and Standards

How To

Functional Info

Background Info

JMRI Code: How to Use the Logging Facilities

This page provides a little information on how JMRI logs error, status and debugging information.

For other details on JMRI internals, please see the technical pages.

JMRI uses the Jakarta Log4J package to handle logging from individual classes. Specifically, as of Summer 2015, we use version 1.2.15; note that's not the most recent one! We use that via SLF4J framework. (For libraries that use the java.util.logging framework, e.g. jmDNS, we also use the jul-to-slf4j adapter)

Logging Levels

This combination provides several levels of messages:
Level Code Fragment Use
ERROR log.error(..) Indicates the desired operation is not going to happen, and should explain why. ERROR is meant to be used only for serious problems which should get some attention every time they happen, typically indicating a possible fault in JMRI itself.
WARN log.warn(..) The program is still operating, sort of, but something has gone wrong; often used to say "This operation may not have done all you wanted". Use WARN when the cause of the problem is incorrect user input.
INFO log.info(..) Routine messages you want to see in normal operation. Keep these to a minimum please, there shouldn't be any after the program has started up.
DEBUG log.debug(..) Detailed messages, only used in debugging. There is a lot of this, and turning it all on can slow the program down significantly.
TRACE log.trace(..) Very detailed messages, even more than DEBUG, used for voluminous debugging (e.g. every character in a transmission) that would normally be off even when debugging. Typically only turned on for one class at a time due to volume and performance issues.

Configuration

By convention, JMRI applications will attempt to initialize Log4J using a "default.lcf" logging control file. JMRI contains a version of the default.lcf file with extensive comments. (When running JUnit tests, a similar "tests.lcf" file is used.) Although JMRI distributes the default.lcf file in the "Program Directory", it's better if you put your own version in the "Preferences Directory, which can be found by selecting the "Locations" item in the main help menu) The rest of this section describes the contents of a logging control file using the contents of default.lcf as an example. As

The line:

 log4j.rootCategory= INFO, A1, T, R
controls where logging output goes. Later in the file, there are "appenders" A1, T and R defined that write log messages to: The files are stored in directory specified by the jmri.log.path parameter, which is turn defaults to the log subdirectory in the preferences directory.

The "default.lcf" file also determines the format of the output by setting the "layout" parameter(s).

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d{ISO8601} %-37.37c{2} %-5p - %m [%t]%n

An example of the default format:

2015-10-28 20:31:52,307 jmri.jmrit.powerpanel.PowerPane  WARN - No power manager instance found, panel not active [AWT-EventQueue-0]
The columns are:

Coding

To log messages from a class named MyClass, add this to the bottom of the class's .java file:
        private static final Logger log = LoggerFactory.getLogger(MyClass.class);

and add imports for org.slf4j.Logger and org.slf4j.LoggerFactory in your imports section:

        import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
It's also OK to combine those into the fully-qualified form:
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MyClass.class);

(If logging is removed (e.g. commented out), it's OK to comment out the log definition so that it can be easily added back later on.)

Then for each message to log insert a line like:

        log.debug("message");

Messages that are not just an explicit string should use this form instead for variables:

        log.debug("Found {}", numberEntries);

The string operations to build the actual error message (in this case, combining "Found" and the numberEntries argument) are only done if the message is going to be stored in the logs, which saves a lot of time during normal (non-debug) execution.

Note It slows down logging to add .toString() to any fields or method results, so do not add that. SLF4J takes care of ensuring that toString() is called correctly.

If it would be computationally expensive to pass a parameter into the log, use following form so the program does not waste time calculating parameters (in this case, calling numberEntries() to get a value to pass to the function call):

        if (log.isDebugEnabled()) {
            log.debug("Found {}", numberEntries());
        }

If you want to emit a warning only the first time it happens, there's a service method that will handle that for you:

        Log4JUtil.warnOnce(log, "The warning with arguments {} {}", "A", "B");
Note: The warnOnce method requires a bit of special handling in the unit and CI tests, see testing documentation.

Exceptions should be logged as:

        log.error("my local text"+exception.getLocalizedMessage(), exception);

to include the user readable description from the exception itself, plus all its traceback information.