Powerful Logging in Java ME

   
By Johan Karlsson, originally published November 2009, updated February 2010  

This technical article explains how to add logging to your MIDlets using the Microlog open-source logging library.

Contents
 
Getting Started with Microlog
Using the Bluetooth Server
Logging Destinations
Log Levels and Filtering
Summary
 

Introduction

You have just developed your new cool MIDlet, the one that is going to rule the world. It runs like a well oiled machine on the emulator. But when you download and install it on your target device, the MIDlet seems to take ages to start up. Finally, you see the splash screen. Oh no! The MIDlet crashes and you get an error pop-up saying "Application Error," and the MIDlet shuts down. What happened? This is not the way you pictured it. Is there something that shows what really happened behind the scenes?

Have you been in a situation like this? Have you ever wanted to log from your MIDlet? Read on, and I will teach how to add powerful logging to your MIDlets.

Getting Started with Microlog

You may have considered doing your own logging facility. But why not use an existing solution? An easy-to-use solution exists: The Microlog open-source logging library. It is based on the well known Log4j API or logging library, but has been created from the ground up with Java ME limitations in mind.

The library is released with Apache Software License V2. Thus you can use it in your own open-source projects, as well as in commercial applications. But let's get down to business and do some programming.

To add logging to your MIDlet, follow these steps:

  1. Download Microlog.
  2. Add the Microlog jar to your project.
  3. Create your MIDlet with logging statements.
  4. Configure Microlog.
  5. Build and run your MIDlet.

I will not go into detail about the first two items. Please refer to the instructions for the specific IDE that you are using. Starting at step 3, the source code is as follows. (For your convenience, I have attached all example source code files and configuration files.)

SimpleLoggingMidlet.java

package com.jayway.midlet.techtip;

import java.io.IOException;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.midlet.*;
import net.sf.microlog.core.Logger;
import net.sf.microlog.core.LoggerFactory;
import net.sf.microlog.core.PropertyConfigurator;

/**
 * The SimpleLoggingMidlet shows how simple it is to add logging to a midlet.
 * 
 * It has a simple GUI that let you try out logging.
 * N.B. there has to be some kind of GUI, otherwise it wont work in Suns WTK.
 *
 * @author Johan Karlsson
 */
public class SimpleLoggingMidlet extends MIDlet implements CommandListener {

    // A logger instance for this class
    private static final Logger log = LoggerFactory.getLogger(SimpleLoggingMidlet.class);
    private Display display;
    private Form form;
    private Command logCommand = new Command("Log", Command.OK, 1);
    private Command exitCommand = new Command("Exit", Command.EXIT, 1);

    /**
     * Constructor.
     */
    public SimpleLoggingMidlet() {
        // Configure Microlog
        PropertyConfigurator.configure();
    }

    /**
     * @see javax.microedition.midlet.MIDlet#startApp()
     */
    public void startApp() {
        log.info("Starting application");

        if (form == null) {
            log.debug("Creating a new Form");
            form = new Form("Simple Logging");
            form.addCommand(logCommand);
            form.addCommand(exitCommand);
            form.setCommandListener(this);
            display = Display.getDisplay(this);
        }

        display.setCurrent(form);
    }

    /**
     * @see javax.microedition.midlet.MIDlet#pauseApp() 
     */
    public void pauseApp() {
        log.info("Pausing application");
    }

    /**
     * @see javax.microedition.midlet.MIDlet#destroyApp(boolean)
     */
    public void destroyApp(boolean unconditional) {
        log.info("Destroying application");

        // Shutdown Microlog gracefully
        LoggerFactory.shutdown();
    }

    /**
     * @see CommandListener#commandAction(javax.microedition.lcdui.Command, javax.microedition.lcdui.Displayable) 
     */
    public void commandAction(Command command, Displayable displayable) {
        if (command == logCommand) {
            log.debug("User pressed the log button.");
        } else if (command == exitCommand) {
            log.debug("Exit button was pressed");
            notifyDestroyed();
        }

    }
    
}
       

As you can see, the code is really simple. You create a Logger as a constant, that is, a static final class variable. For each class that you need to log, you add the same line of code except that you change the argument to match the class that you are using the logging in. The class name is then used as the name for the logger. This is handy within the log to distinguish between where the actual logging was performed. To do the actual logging, just add this simple line of code:

log.debug();

The debug() method is called when logging at debug level. There are other methods for different levels. How the level works is discussed later in the article. The set-up is equally simple, you add one line of code:

PropertyConfigurator.configure();

Notice that this should only be done once in an application, and this is why it is recommended to put it in the constructor of the main class. When developing a MIDlet, that is, it should always be in the constructor of the MIDlet. This ensures that it executes only once in the MIDlet's lifetime.

To ensure that Microlog shut down correctly, you should add a call to LoggerFactroy.shutdown() in the destroyApp() method. The PropertyConfigurator looks for a file called microlog.properties that is in the root of your jar. Put the properties file in your resource directory. This way the properties file is packaged in the JAR file when you build your project. Edit it to look like the example below.

# This is a simple Microlog configuration file
microlog.level=DEBUG
microlog.appender=ConsoleAppender
microlog.formatter=SimpleFormatter
       

The PropertyConfigurator reads the property file that is packaged in the JAR file. By default it looks for the microlog.properties file, but it is possible to set your own file name. By using the PropertyConfigurator, you avoid putting predefined values in your code. This is good. On the other hand you must rebuild the JAR file each time you change the properties file.

Another solution is to use the MidletPropertyConfigurator. It first searches the JAD file and then checks the properties file for properties. Consequently, it is possible to change the Microlog configuration without rebuilding the JAR file. This is a very handy solution. If you forget to configure Microlog, it configures itself automatically. It is a simple configuration, using the ConsoleAppender with the SimpleFormatter -- the simplest possible logging that is available in Microlog, but it works on all devices.

Let me show how simple it is to change the logging. All you have to do is change the configuration file and rebuild the jar. The following example shows how to add logging to the record store and change the layout of the log output.

# Another simple Microlog configuration file. We now log to two different
# destinations; the console and the record store.
microlog.level=DEBUG
microlog.appender=ConsoleAppender;RecordStoreAppender
microlog.formatter=PatternFormatter
microlog.formatter.PatternFormatter.pattern=%c [%P] %m %T
       

We have added the RecordStoreAppender to log to the record store. The formatter has changed from the SimpleFormatter to the PatternFormatter. The SimpleFormatter uses a predefined pattern for logging output. This is a good starting point, but in most cases you want to configure the logging output yourself.

The PatternFormatter lets you decide on how the logging pattern should look like. In this case we print the class name, the priority, the message and the Throwable object. If no Throwable is logged, this part is skipped.

Now compile and run the MIDlet once more, but with the new configuration file. The MIDlet logs to the record store while executing the MIDlet. Afterwards, when you have exited the MIDlet, you start the prepackaged record store log viewer. This is how it looks in action:

 

Using the Bluetooth Server

But that is not all! Many developers use Bluetooth to send their MIDlets to the target, to avoid the tedious need to connect and disconnect USB cables. If you are one of those developers, you might want to use the BluetoothSerialAppender. It sends the log directly to your computer from your target device. Looking at the log on your computer is much more convenient than viewing the log on a small mobile screen.

The following picture shows the Bluetooth server in action. Notice that it is possible to connect several clients at the time. I am using an older Sony Ericsson P910 device (in general, Microlog executes on older devices without problem).

 

Logging Destinations

But wait, there is even more. You have a myriad of logging destinations available in Microlog. Some of them are on-device, while others send the log to a remote device. In early stages of development, it is most convenient to use the on-device logging (or Bluetooth). Later in the development cycle, it is often more suitable to use remote device logging.

My favorite on-device logging destination is a file. On many devices, it is possible to read the log on the device. Most of the time I transfer the file to my computer and view it in my favorite editor. (If you are a dedicated Unix lover, use grep to find what you are looking for.)

What
When to use it
ConsoleAppender
Use the emulator and log in the output window.
CanvasAppender
To view the log on the device.
FormAppender
To view the log on the device.
RecordStoreAppender
To view the log on the device.
FileAppender
To have a log to view on your computer.
MemoryBufferAppender
To view the log on the device.
BluetoothSerialAppender
To log from the device to your computer and view the log in real time. A server with a simple GUI is provided with Microlog.
HttpAppender
To log to a HTTP server. A sample HTTP server is provided with Microlog.
SMSBufferAppender
To send an SMS when something goes wrong.
MMSBufferAppender
To send an MMS or e-mail when something goes wrong.
SocketAppender
To send to a socket server. A sample socket server is provided with Microlog.
SyslogAppender
To log to a syslog daemon on a remote server. A syslog daemon is a standard logging server that is available on the most common operating systems.
S3BufferAppender/S3FileAppender
To log to a cloud, such as an Amazon S3 account. The file can be viewed by any S3 browser.
 

Log Levels and Filtering

One of the most important functions in a logging framework is the ability to filter your logging statements, just like Log4j. In Microlog, this is solved by assigning different levels to your logging statements.

It is important to use the right level when logging. Otherwise the logs will be flooded and performance will most likely be affected in a negative way.

When you set a level in Microlog, you logs everything at the specified level and above. For example, if you choose to log at level DEBUG, everything that is between level FATAL to level DEBUG will be put in the log. The following examples use one logging level for the entire application.

Since version 2.0 of Microlog, it has been possible to assign different levels to different classes. Let us assume that you have a MIDlet that is getting a big, that is, you have many classes. The logs get flooded by logging outputs, but you do not want to remove all important logging statements as most of them are there for a good reason. In such cases, it is possible to choose to only enable those classes that are relevant at the moment. For example, you are hunting down a bug and you know that is in a particular package. Look at this example:

microlog.rootLogger=ERROR, A1, A2

microlog.appender.A1=ConsoleAppender
microlog.appender.A2=RecordStoreAppender

microlog.appender.A1.formatter=PatternFormatter
microlog.appender.A1.formatter.pattern=%c{1} [%P] %m %T

microlog.logger.com.jayway.midlet.techtip=DEBUG
       

First, you set the root logger: You set the logging level and specify two appenders. We set the level to ERROR, which always is a good choice for the root logger. Whenever a logging is done at ERROR or FATAL, it gets logged.

You give the appenders symbolic names, in this case A1 & A2. The setup of the appenders and the formatter is the same, except that we insert the symbolic name. Notice that we we use the default settings for the RecordStoreAppender. At the end we override the ERROR level by setting the DEBUG level to all classes in the com.jayway.midlet.techtip package.

The property for setting the level is the string microlog.logger concatenated with the name of the package. It is possible add several packages and their special level. It is even possible to define the level for a set of packages. For example, you can set a special level for com.jayway and its inherent packages. All other packages will inherit the ERROR level from the root logger.

The following table shows the different logging levels available in Microlog. The first level, FATAL, always has the highest priority.

Priority
Log Level
When to Use
1
FATAL
Severe error that causes the application to terminate prematurely.
2
ERROR
An error that causes the application to malfunction, but it could continue.
3
WARN
Warns that something happens that might cause problems in the future.
4
INFO
Interesting information, like when the application starts, exits, etc. In a MIDlet it should be the first statements in the startApp(), pauseApp() and destroyApp()
5
DEBUG
Detailed information about the flow in the application.
6
TRACE
Even more detailed information about the flows and variables.
 

Summary

This article shows how easy it is to add powerful logging to your MIDlet. It is done by simply adding the jar and a configuration file to your project. Once this is done, it is a matter of editing the configuration file to your liking.

Logging is an essential tool for embedded developers, and a nice companion to a debugger. It will help you in situations where using an emulator is not a viable solution. In Microlog, it is also possible to use in a headless environment, that is, an embedded device without a GUI.

For more tips and tricks, see the official Microlog blog. Happy logging!

Resources

 
Rate This Article
 
 
Comments
Terms of Use