Tuesday, July 10, 2007

Sending logs to the server using custom LogTarget in flex

In flex you can use the global trace() method to output debug info from a flash. Unfortunately, these data ends up either in the debugger console, or in a local file, if setup correctly on the client. Most user installations will never get access to the data. When going into production with a flash application, it is nice to be able to see client-side logs at the server-side.

Luckily, this can be nicely done using the flex logging framework.

The Remote Part
The webservice interface I am calling in on, is this:

public interface ClientLog {
void acceptLog(String message);
}

I wont go into detail on that one. To see how to call a webservice from AS3, see my previous post on the subject.

Implementing a new LogTarget
To be able to simply use the existing logging API and still have logs sent to the server, we need to implement a LogTarget, that sends logs to the server side. Simplest thing seems to be extending LineFormattedTarget , and override its internalLog method.

import com.foo.bar.remote.ClientLogService;
import mx.logging.targets.LineFormattedTarget;
import mx.logging.LogEvent;
import mx.logging.LogEventLevel;
import mx.logging.ILogger;
import mx.core.mx_internal;

use namespace mx_internal;

public class ServerLogTarget extends LineFormattedTarget {
private var server : ClientLogService;

public function ServerLogTarget() {
super();

// change the defaults for these
super.fieldSeparator = "##";
super.includeTime = true;
super.includeDate = true;
super.includeCategory = true;
super.includeLevel = true;
super.level = LogEventLevel.ALL;

server = ClientLogService.getInstance();
}

public override function logEvent(event : LogEvent) : void {
// Take care to NOT include mx.*, as code in std lib issues log calls,
// which will call this target, which will send a message to server
// (through rpc operation), which will log to this again, ... until StackOverflowException
var category : String = ILogger(event.target).category;
if (category.indexOf("mx.") != 0) {
super.logEvent(event);
}
}

/**
* Super class calls this method when it has formatted the message
* and wants to send it somewhere.
* The default implementation does nothing.
*/
mx_internal override function internalLog(message:String) :void {
if (server != null) {
server.logToServer(message);
}
}
}

Things to note:
  • I import and use the mx_internal namespace, to be able to override the internalLog method
  • The ClientLogService class is simply a wrapper around my webservice (see previous post about calling a webservice from AS3)
  • The logEvent method is overridden, to check if it is a "mx.*" class logging, to avoid "infinite" recursion
Using the new LogTarget
Only thing you need now, is to configure the flex logging framework with the new target. This includes constructing a target instance, and setting filters on it, for the logs you want on the server.

var logTarget:ServerLogTarget = new ServerLogTarget();
logTarget.filters = ["com.foo.bar.*"];
Log.addTarget(logTarget);

One possible place to execute the above code is in the creationComplete event on the main mxml file.

Using the logger is then as simple as this:

Log.getLogger("com.foo.bar.Bleech").info("Hello, server!");

With intelligent use of the logger names and proper filters, you can easily enable only some logs to go to the server side. All without changing any logging code around in the application.

No comments: