Saturday, July 04, 2009

GWT Logging and JSNI

Logging is an essential component of any traditional Java environment, without it, tracking down issues would be time consuming. Unfortunately, on the browser, there's no standard place in which to output log messages. Sure, one solution could be to display a dialog box when something goes wrong, but normally you want to dsplay such things as debug messages which just won't work in a dialog solution.

As a solution to this most browsers provide some kind of logging console exposed through a JavaScript API. One of the best known, and possibly most widely used, is Firebug a JavaScript debug enviroment plugin for Firefox; others includeDragonFly for Opera and the new debug console for Internet Explorer 8. Whilst all these facilities are fantastic for direct JavaScript development, how do we access them from a GWT application?

Here I introduce my simple but effective GWT Logging Module that automatically detects the available browser logging system and connects to it. It was developed as a way of demonstrating GWT JSNI usage but developed into a framework which I use today.

If your too impatient are simply don't want to learn about JSNI, the full Logging module is available ready to use here and if your interested in the source, it's available as an Eclipse project here.

Using The Framework

Firstly the logging module needs to be added to your Eclipse build path, and then the following to your GWT module descriptor :

<inherits name="com.maddison.gwt.logging.Logging"/>

This single line will instantiate the Logging system when the application starts at which point you will see a "Logger Started" message displayed on whichever logging system your browser has installed. To access the Logger from your application simple use the following line :

private static final Logger LOG = Logging.getLogger();

All the logging methods of Logger are now available, for example :

private static final Logger LOG = Logging.getLogger();

public void doSomething() {
 LOG.info("Hello World");
}

The following logging systems are supported:

  • Opera -> DragonFly
  • Firefox -> Firebug
  • GWT hosted mode -> GWT console
  • IE8 -> Debug console
  • Chrome -> JavaScript console

Filtering Logging

A minimum logging level can be specified either as a URL parameter or as a meta tag parameter. The following shows example these:

....index.html?minLogLevel=WARN

or

<head>
 <meta name="minLogLevel" value="WARN"/>
</head>

In both the above examples only warning or higher severity messages will be logged. The severity level's are (most severe first) NONE, ERROR, WARN, INFO, DEBUG, ALL.

How It works

As mentioned in the introduction, this logging system was developed whilst building a JSNI tutorial. With this in mind lets take a look inside one of the Logging implementations, the Firebug logger.

In a standard JavaScript application the following code would log a warning to the Firebug logging console:

 console.warn(message);

Unfortunately, since Java is a typed language, this console object isn't available to us, however GWT provides a way around this in the form of JSNI methods. JSNI methods provide a way for us to write JavaScript directly into GWT classes. For example, the following shows the JSNI method for logging a Firebug warning :

private static native void firebugWarn(String message) /*-{
 console.warn(message);
}-*/;

As you can see, the method is marked as native and the body of the method is enclosed in the /*- and -*/ markers, the code between which will be copied to the compiled GWT application.

The isFirebugAvailable method is another place JSNI is used to good effect. In the logging system I've used the "Object Detection" pattern to determine the browser logging capabilities, here's how this is used in the Firebug logger:

public static boolean isAvailable() {
 return isFirebugAvailable() && BrowserUtils.getUserAgent().contains("mozilla");
}

private static native boolean isFirebugAvailable() /*-{
 if (typeof console != "undefined") {
  return true;
 } else {
  return false;
 }
}-*/;

Here we're checking to see if the console object is available to us (it won't be if Firebug isn't installed), and as an extra check we make sure we're running on Firefox

Deferred Binding

This logging framework makes decisions regarding which logger to use at runtime, however GWT offers another solution known as Deferred Binding. Deferred Binding allows the compiler to make decisions as to what to include in the final application at compile time. For example, if the framework was to use deferred binding, the GWT compiler would produce a separate application for each instance of the Logger, each version ONLY including the logger for the particular browser version. The bootstrap code would check the browser version and load the correct version of the compiled JavaScript. The idea is that the resulting Javascript is not only faster (because certain decisions don't have to be made at runtime), but also smaller.

This sounds great on paper, however in reality it does lead to longer compile times, and if there are any other parts of the application also using Deferred Binding it can lead to quite a few versions (known as permutations in GWT) of the application being created. For example, if there is another Deferred Binding flag that's either on or off, the GWT compiler will create "Number of Loggers" 6 x 2 = 12 versions of the application!

Deferred binding has it's place, however since all the loggers are small and only one decision has to be made when the application starts, I decided Deferred Binding just wasn't worth the payoff.

And that's it!

Originally this post had a step by step code walk through, however I later cut it down to just focus on JSNI. If your interested in seeing just how the framework works then please feel free to download the Eclipse project here. Just a quick note about the project; I don't use the GWT application creator but create GWT project by hand and instead of the project including the GWT libraries I use an Eclipse library (called "GWT") added to the project classpath to bring in the GWT libraries.