Monday, February 1, 2010

Logging to GUI by Writing Custom log4j Appender

All applications need logging. Logging means presenting useful information to the user. Logging can be configured separately from the code that emits logging information.

Apache log4j is a logging implementation library for Java. It can be used directly, however to make our application more portable, we usually use logging abstraction. I recommend Apache Commons Logging for this.

The most popular logging configurations are:
  • printing messages to standard console output
  • appending messages to a log file
However there is one popular use of logging that is seldom talked about, and that is logging to GUI.

For example, you have a text box and you want to log messages to it.

Of course you can do it the old fashioned way, by getting the text box object and appending messages directly to it. But other than it is not portable, and all of your code will be dependant on that text box object.

A more robust resolution would be to use log4j's flexibility. Here I will prove to you that flexibility does not have to equal complex.

log4j since version 1.2 adds a feature that is dynamic logging configuration at runtime. We'll use this feature to add a GUI Appender to log4j. An Appender's job is to take action of a log event. In our case, displaying the log message to a text box.

See the code below:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.spi.LoggingEvent;

public class MainConsoleFrame extends javax.swing.JFrame {

    private Log log = LogFactory.getLog(MainFrame.class);
    private Appender guiAppender;
    ...

    public AuthoringConsoleFrame() {
        initComponents();
        
        // Setup logging to GUI
        final MainFrame frame = this;
        guiAppender = new AppenderSkeleton() {
  
     @Override
     public boolean requiresLayout() { return true; }
  
     @Override
     public void close() { }
  
     @Override
     protected void append(LoggingEvent event) {
             frame.consoleMessagesArea.append(layout.format(event));
         consoleMessagesArea.setCaretPosition(consoleMessagesArea.getText().trim().length() - 1);
            }
        };
 guiAppender.setLayout(new PatternLayout("%5p|%40.40c|%m%n"));
        Logger.getRootLogger().addAppender(guiAppender);
        
        // Log like this...
        log.info("Hello world!");
    }
    
    @Override
    protected void finalize() throws Throwable {
     Logger.getRootLogger().removeAppender(guiAppender);
     super.finalize();
    }

    ...
}    

Please note that I skipped a lot of boilerplate code in the above.

After setting up the Appender, you probably notice that the code for logging is now very simple. It uses Apache Commons Logging which means the application will work with any logging implementation. This makes code very portable, even if you remove the textbox or move code to a different application, logging code should just work.

I hope this post is helpful to you. Let me know if you have ideas or questions.