Sencha Inc. | HTML5 Apps

Blog

Getting More out of Logging with GXT

June 20, 2013 | Brandon Donnelson and Colin Alworth

Introduction

Logging with GXTLogging can be an invaluable tool in the application debugging tool chest when it isn’t enough to have a great debugger and good test coverage. As applications grow more complex, logging can be quite handy, so you can see what other interactions might be occurring due to seemingly unrelated logic and events. Logging can also be useful for other processes, such as getting feedback about possible bugs while still writing code, measuring performance of various parts of the application, and tracking how the application is used.

When large atom smashers spit out loads of data, they use a hypothesis to drill down and filter the data. The same goes for logging: unless a hypothesis is developed while coding source, the logging is going to look like noise. Having this goal in mind while writing your application will enable you to produce logging details that will be helpful later when you want to filter through the output generated by your application.

Logging in GWT

There are three main logging mechanisms built into GWT, each with a slightly different purpose and use case:

  • GWT.log is for Development Mode only. When invoked, it adds a message to the Development Mode console. There are two methods that can be called, log(String) and log(String, Exception). The former writes out simple text as an info statement, the latter as an error, and it draws the full stack trace of the exception when the message is selected. This is being used less now that Super Dev Mode is beginning to be adopted, since these messages will not show up.
  • The Lightweight Logging system is meant to be a low-level, web-mode specific mechanism to track details like application startup, simple performance figures, and can be enabled or disabled without recompiling the application. This means that these log statements never compile out, so they should be used sparingly.
  • The Java Logging facilities are partially emulated in GWT, allowing access to most of the same calls that you generally have in non-GWT Java code. This can be used in dev or web mode, and it has a number of configuration options within your module so you can decide where that logging should go. This article will focus on this tool.

java.util.logging

The java.util.logging tools behave the same on the client and server—instances can be retrieved from a call to Logger.getLogger(String), which should be given the name of the current class name, and typically are kept in a private static final field to avoid getting or making new loggers each time they are needed.

If your project includes GXT, logging is already present but completely disabled. Logging is enabled in the com.sencha.gxt.core.Core module—this line adds the necessary classes to the project, but forces them all to completely compile out:

 
<inherits name="com.google.gwt.logging.LoggingDisabled" />
 

To enable logging add this definition and set the value flag to true. The possible values are TRUE or FALSE—with the LoggingDisabled inherits statement above (or GXT in your app), here is how you enable logging:

 
<set-property name="gwt.logging.enabled" value="TRUE"/>
 

There are seven levels of logging available: severe, warning, info, config, fine, finer, finest. The higher in that list, the more important the message is —most logging configurations display the top two, and the rest are often left out except in specific situations.

 
<set-property name="gwt.logging.logLevel" value="INFO"/>
 

Next, there are logging handlers, the mechanisms that GWT uses to display or persist the logged messages. Typically on the server, you would write the log messages to one or more files, but on the client we don’t have that capability, so we need to be more selective in how we get the log messages. These fall into several categories, and can individually be ENABLED or DISABLED:

  • Dev mode specific - these only work when in dev mode, as they use either GWT.log or the Java console to emit their messages:
     
    <set-property name="gwt.logging.systemHandler" value="ENABLED" />
    <set-property name="gwt.logging.developmentModeHandler" value="ENABLED" />
     
  • Browser console - Firebug, Chrome’s Inspector, IE’s Developer Toolbar, etc. These show the messages in the browser in either web or dev mode, but are not visible unless the user opens one of these consoles directly.
     
    <set-property name="gwt.logging.consoleHandler" value="ENABLED" />
    <set-property name="gwt.logging.firebugHandler" value="ENABLED" />
     
  • Browser popups - the idea can be useful to enable users to see what is in the log, though the default implementation always renders below most GWT and GXT widgets, so isn’t often very useful; more on this at the end.
     
    <set-property name="gwt.logging.popupHandler" value="ENABLED" />
     
  • And finally, the server - we can send messages to the server, and ask it to log them as it normally would for later review. This option enables you to save logs to a file, though remember that all users will be logging together to the same file. This is the only option that is disabled by default, as it requires a properly configured server to receive those logs, using the com.google.gwt.logging.shared.RemoteLoggingService RPC interface.
     
    <set-property name="gwt.logging.simpleRemoteHandler" value="DISABLED" />
     

Sample Application

This is a sample that uses most of the logging defaults. All the browser and dev mode consoles are used and remote logging is left disabled, and the popup is disabled since it won’t work well with GXT. The log level is set to FINEST to report all possible messages, and additionally GXT itself is told to emit all internal details.

 
<!-- In the Project.gwt.xml file, after the GXT inherits statement -->
<!-- Ask for all possible messages -->
<set-property name="gwt.logging.logLevel" value="FINEST" />
<!-- Write messages to browser consoles and to the jvm and dev mode -->
<!-- Note that these are the defaults, so we don’t actually need to list them -->
<set-property name="gwt.logging.consoleHandler" value="ENABLED" />
<set-property name="gwt.logging.firebugHandler" value="ENABLED" />
<set-property name="gwt.logging.developmentModeHandler" value="ENABLED" />
<set-property name="gwt.logging.systemHandler" value="ENABLED" />
<!-- Disable the popupHandler when using GXT layouts, they draw above -->
<set-property name="gwt.logging.popupHandler" value="DISABLED" />
<!-- Leave RPC logging disabled, as we aren’t setting that up in this example -->
<set-property name="gwt.logging.simpleRemoteHandler" value="DISABLED" />
<!-- Ask GXT to log all internal details -->
<set-property name="gxt.logging.enabled" value="true" />
 

Below is a simple logger example. First the logger is instantiated and later logger.log(...) is called to produce logging output according to the options selected in the application module.

 
// Example using logging
public class TestLogging implements EntryPoint {
  // instantiates a logger
  private static final Logger logger = Logger.getLogger(TestLogging.class.getName());
 
  @Override
  public void onModuleLoad() {
    logger.log(Level.INFO, "Phase A: So far so good.");
    logger.log(Level.WARNING, "Did this forget something?");
 
    TextField field = new TextField();
    String value;
    try {
      value = field.getValueOrThrow();
    } catch (ParseException e) {
      logger.log(Level.SEVERE, "Field Error", e);
    }
 
    if (logger.isLoggable(Level.INFO)) {
      // Anything in this statement will not get compiled when logging is off. 
      logger.warning("Do something extra while logging.");
    }
 
    if (GXTLogConfiguration.loggingIsEnabled()) {
      // Anything in this statement will not get compiled when logging is off. 
      logger.info("Do something GXT logging is on.");
    }
  }
}
 

More Configuration

By disabling or enabling the logging feature entirely, you are telling the compiler to either leave all logging statements in or remove them entirely. The log level is less specific; even if you set the log level to WARN, just as in non-GWT Java, some code might change it back to FINEST by calling Logger.getLogger("").setLevel(Level.FINEST). This further enables you to set different log levels for different parts of your application. In the same way, you can specify handlers for particular loggers, setting one log level for internal code and another for third-party code, etc. But once logging has been disabled, that code should compile out entirely.

Building Your Own Log Handler

Building your own handler is simply a matter of implementing java.util.logging.Handler and doing something with the data. Here is an example in GXT that generates a Grid that could be added to part of the application to show all log messages. Note that until you add the handler to a logger (and in this case, probably the root logger), it will not start recording logged messages.

 
public class GridHandler extends Handler implements IsWidget {
  public interface LogRecordProperties extends PropertyAccess<LogRecord> {
    @Path("millis")
    ModelKeyProvider<LogRecord> key();
 
    ValueProvider<LogRecord, String> message();
    ValueProvider<LogRecord, String> loggerName();
    ValueProvider<LogRecord, Long> millis();
    ValueProvider<LogRecord, Level> level();
  }
  private static final LogRecordProperties properties = GWT.create(LogRecordProperties.class);
  protected final ListStore<LogRecord> store = new ListStore<LogRecord>(properties.key());
  private Grid<LogRecord> grid;
 
  public GridHandler() {
    grid = getLogRecordGrid(getLogRecordColumnModel(getColumnConfigs()));
  }
  @Override
  public void publish(LogRecord logRecord) {
    store.add(logRecord);
  }
 
  //these methods do nothing, since we don't need to think about files
  @Override
    public void flush() {
  }
 
  @Override
    public void close() {
  }
 
  @Override
    public Widget asWidget() {
    return grid;
  }
 
  private Grid<LogRecord> getLogRecordGrid(ColumnModel<LogRecord> columnModel) {
    Grid<LogRecord> grid = new Grid<LogRecord>(store, columnModel);
    grid.getView().setForceFit(true);
    return grid;
  }
  private ColumnModel<LogRecord> getLogRecordColumnModel(List<ColumnConfig<LogRecord, ?&gt;> columns) {
    return new ColumnModel<LogRecord>(columns);
  }
  protected List<ColumnConfig<LogRecord, ?&gt;> getColumnConfigs() {
    List<ColumnConfig<LogRecord, ?&gt;> columns = new ArrayList<ColumnConfig<LogRecord, ?&gt;>();
    columns.add(new ColumnConfig<LogRecord, String>(properties.loggerName(), 10, "Logger"));
    columns.add(new ColumnConfig<LogRecord, Level>(properties.level(), 10, "Level"));
    columns.add(new ColumnConfig<LogRecord, String>(properties.message(), 10, "Message"));
    columns.add(new ColumnConfig<LogRecord, Date>(new LongToDateValueProvider<LogRecord>(properties.millis()), 10, "Timestamp"));
    return columns;
  }
 
  public void clear() {
    store.clear();
  }
  public static class LongToDateValueProvider<M> implements ValueProvider<M, Date> {
    private final ValueProvider<M, Long> base;
    public LongToDateValueProvider(ValueProvider<M, Long> base) {
      this.base = base;
    }
    @Override
    public String getPath() {
      return base.getPath();
    }
    @Override
    public Date getValue(M object) {
      return new Date(base.getValue(object));
    }
    @Override
    public void setValue(M object, Date value) {
      base.setValue(object, value.getTime());
    }
  }
}
 

Conclusion

Logging adds additional process output information which can provide invaluable process information during development, after development and while debugging. It can be completely compiled out, or added back to assist in debugging in production, and can be customized for your particular application to render exactly the details you need.

Other References

Written by Brandon Donnelson and Colin Alworth
Brandon is a member of the Sencha GXT team and enjoys interacting with the GWT community. His passion is helping developers get their jobs done. He has development experience in Java, Eclipse Plugin, IDEA Plugin, GWT, Cloud, Sencha GXT and more.

Colin is a member of the Sencha GXT team and has worked in the community for a number of years. He has experience in JavaScript, GWT and Sencha GXT.

Share this post:
Leave a reply

Add your comment:

Comments are Gravatar enabled. Your email address will not be shown.

Commenting is not available in this channel entry.