JMRI: How to Use the Logging Facilities
Author: Bob Jacobsen
Keywords: JMRI log4j logging log

# JMRI Code: How to Use the Logging Facilities

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

For ways to view these log events, see JMRI: Debugging and System Logging

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

JMRI uses the Apache Log4J package to handle logging from individual classes. Specifically, as of Summer 2023, we use Log4J version 2.20 via the SLF4J 2.0.7 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.

The Log4J Level FATAL is not in the SLF4J interface, hence unused by JMRI.

### Configuration

By convention, JMRI applications will attempt to initialize Log4J using a "default_lcf.xml" logging configuration file. JMRI contains a version of the default_lcf.xml file with extensive comments. (When running JUnit tests, a similar "tests_lcf.xml" file is used.) Although JMRI distributes the default_lcf.xml file in the "Program Location", it's better if you put your own version in the "Settings Location", which can be found by selecting the "File 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.xml` as an example.

JMRI < 5.5.4 uses the default.lcf file to control logging configuration. Open this file with a text editor to make changes as per the file comments.

In the file, there are three "Appenders" A1, T and R which define where the log events go.

- A1 - the console on the local computer, really stdout/stderr, which is also available via the Console item on the JMRI Help menu
- R - a single file. By default, this is called "session.log". This starts anew each time JMRI is restarted.
- T - a set of rolling files. The current file is called "messages.log". When it reaches 1MB, it rolls to a new file. Two older versions are kept. Note that this continues through program restarts, so you can see any messages logged at the very beginning or end of the program.

#### Standard Output Logging

While the program is running, debugging information is printed to "standard output", which is in different places depending on what operating system you're using and how you started the program.
On a Macintosh classic, it appears in a window that's created with the first error message. With Mac OS X, it may appear in a new window, or you may have to launch the "Console" application to see it.
On Windows, you may have to run one the "Debug" batch files to see the error messages; look for them in the application's directory.

Generated log files are stored in the directory specified by the jmri.log.path parameter, which defaults to the log subdirectory in the preferences directory.

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

            <Console name="A1" target="SYSTEM_ERR">
                <PatternLayout pattern="%d{ABSOLUTE} %-37.37c{8} %-5p - %m [%t]%n"/>
            </Console>

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:

- `2015-10-28 20:31:52,307` - local time the message was logged
- `jmri.jmrit.powerpanel.PowerPane` - the place in the code (class name) that emitted the message
- `WARN` - the severity of the message
- `No power manager instance found, panel not active` - the message itself
- `[AWT-EventQueue-0]` - name of the thread that emitted the message

To define the level for a particular Logger, towards the bottom of the file, with the other Logger elements, add the logger Category name and Level, eg.

            <Logger name="jmri.jmrit.operations" level="DEBUG"/>

The levels ( INFO, DEBUG etc. ) that JMRI logs at can be changed via Display / Edit Log Categories , however these changes do not propogate to the Logging Configuration File and do not persist between sessions.

### Coding

To log messages from a class named MyClass, add this to the bottom of the class's .java file:

            private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MyClass.class);

Some classes import both org.slf4j.Logger and org.slf4j.LoggerFactory which is fine, although 2 imports to support 1 line of code may appear excessive to some.

(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 or information message only the first time it happens, there are service methods that will handle that for you:

            Log4JUtil.infoOnce(log, "This info message will only be output once.");
            Log4JUtil.warnOnce(log, "The warning with arguments {} {}", "A", "B");

Note: The warnOnce and infoOnce methods require 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.

Logging in JMRI via SLF4J / Log4J can also be achieved with Jython and JavaScript.