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)
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.
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.
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.
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 loggedjmri.jmrit.powerpanel.PowerPane
- the place in the code (class name) that
emitted the messageWARN
- the severity of the messageNo power manager instance found, panel not active
- the message
itself[AWT-EventQueue-0]
- name of the thread that emitted the message
<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.
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.