Remote Log4J in Flex?

Case: You’ve finally made that all-so-snazzy Flex application with DataGrids, Charts, transitions from here to the end of eternety, nice looking validators and all else you can think of. Also, you made it in no-time. The users are thrilled and so is the client. In other words, all is peachy in Rich Client Web Application Land.

Hadn’t been for those re-occuring complaints from the users that “An unknown exception has occured” message with a big warning label pops up from time to time. A nice little feature you made to hide all of those unexpected exceptions that can, but should not, occur in your application. All in all a good thing as we would like to spare the user the experience of reading an unpleasant stacktrace from an external webservice error. But YOU, on the other hand. You would love to get your hands on the stacktrace from that Error that shouldn’t happen. So, how?

In Java-Land this is usually done by incorporating some form of logging that logs messages to file or almost whatever you would like. Most preferred being Log4J. Unfortunately I feel that a just-as-easy solution for Flex/Actionscript has yet to present itself. Until now. As a left-over artifact from the development of Spring Integration, the guys from Prana and Spring has made library called as3commons-logging. The library mimics Log4j in many ways and makes an excellent job doing so. Adding the as3commons-logging swc to your project then makes remote logging a peach. Here’s howto:

Some code

Step 1 of 3
Implement an ILoggerFactory like this. It’s responsibility will be to create Loggers for your classes:

public class RemoteLoggerFactory implements ILoggerFactory
{
        private var remote:RemoteObject;
 
        public function RemoteLoggerFactory(url:String, destination:String="remoteObjectLogger", fault:Function=null)
        {
                remote = createRemote(url, destination);
                remote.addEventListener(FaultEvent.FAULT, fault == null ? onRemoteFault:fault);
        }
 
        public function getLogger(name:String):ILogger
        {
                return new RemoteLogger(name, remote);
        }
 
        /** Creates a RemoteObject using SecureAMFChannel if https */
        private function createRemote(url:String, destination:String) : RemoteObject
        {
                var channelSet:ChannelSet = new ChannelSet();
                channelSet.addChannel(isHttps(url) ? new SecureAMFChannel(null, url) : new AMFChannel(null, url));
 
                var remoteService:RemoteObject = new RemoteObject(destination);
                remoteService.channelSet = channelSet;
 
                return remoteService;
        }
 
        private function isHttps(url:String) : Boolean
        {
                return url.indexOf("https") != -1;
        }
 
        /** Default FaultHandler. Just traces the message. */
        private function onRemoteFault(evt:FaultEvent) : void
        {
                trace("Could not log to server: "+evt.fault.message);
        }
}

Step 2 of 3
And then create the actual Logger which sends log messages to the backend, be that java, php, rails, .NET or name it.

public class RemoteLogger extends DefaultLogger
{
	protected var _level:uint;
	protected var remote:Object;
 
	public function RemoteLogger(name:String, remote:Object)
	{
        super(name);
        this.remote = remote;
	}
 
	override protected function log(level:uint, message:String, params:Array):void {
		if (level >= this._level) {
                        // add datetime
			var msg:String = "[" + new Date().toString() + "]";
			// add the name given to this logger. Usually the Class name
			msg += "[" + name + "]";
			// add message
			msg += " - " + MessageUtil.toString(message, params);
 
			var logLevel:String = LogLevel.toString(level);
			remote.log(logLevel, msg);
		}
	}
}

Step 3 of 3
Next instantiate your factory. Preferably as early as possible, so snapping in something like this at your Main mx:Application will do the trick.

private static var loggerSetup:* = LoggerFactory.loggerFactory = new RemoteLoggerFactory(ROOT_PATH+"messagebroker/amf");

And voila! It is now useable in any of your classes thiswise:

...
private static const logger:ILogger = LoggerFactory.getClassLogger(Jesus);
 
public function Jesus(){
      logger.info("Attention: We constructed Jesus.");
}

A dash of backend

The last but not least is to have a backend that can receive the messages and log them in a human readable way for later error debugging.
Why not splash out a java class with log4j? Well, this will do your trick:
Step 3.5

public class RemoteObjectLogger
{
        private static final String PRE_FLEX = "[FLEX]";
        protected final Log logger = LogFactory.getLog(getClass());
 
        public void log(String level, String message)
        {
                if ("INFO".equals(level))
                        logger.info(PRE_FLEX + message);
                else if ("DEBUG".equals(level))
                        logger.debug(PRE_FLEX + message);
                else if ("WARN".equals(level))
                        logger.warn(PRE_FLEX + message);
                else if ("ERROR".equals(level))
                        logger.error(PRE_FLEX + message);
                else if ("FATAL".equals(level))
                        logger.fatal(PRE_FLEX + message);
                else
                        logger.info(PRE_FLEX + "[" + level + "] - " + message);
        }
}

Try it

Would you like to see it in action? From top to bottom? Well I coded a small example for you to play with using Java as a backend. Svn the code from google-code. Only prerequisite is Maven installed. From there run compileAndRun and you are up and running!

  • http://totalworldannihilation.org/blog Espen Dalløkken

    Depending on “remote logging” sounds like technology looking for a problem, rather than an actual problem resolved by technology. If you follow the design approach from Adobe by letting data drive your application the only errors which should occur are those between client and server. Needless to say a remote logger won’t provide any information if there is a problem with this integration.
    I find that building in client side logging in combination with a “Report error” type button enables remote users to provide you with detailed information about what went wrong. Remote logging sounds like a fun engineering task, but it does not sound like something which provide actual value.

  • Ole Christian Langfjæran

    Truly agree. Logging does not replace TDD or proper exceptionhandling. Neither does logging for loggings sake has any place in Flex what-so-ever. So where would it be most useful?

    My experience of a case where such logging come in handy is sort of “Production debugging”. Where your Flex app contacts an obscure external webservice that from time to time delivers responses that does not compute with the request you sent in. Unfortunately this parses perfectly and your app contiues, although with faulty data. The external-webservice-guys says of course that you are to blame. That’s where I think it would have been a good time to have had the opportunity to switch on logging for the app and see the actual request and response. And put the blame where it should be!

  • Saha

    Hi Ole,

    Your post is very helpful, but i have the following questions and problems, Can you please let me know.

    1) if (level >= this._level)

    I get error here. is it if (level >= this._level)

    2) private static var loggerSetup:* = LoggerFactory.loggerFactory = new RemoteLoggerFactory(ROOT_PATH+”messagebroker/amf”);

    What do i need to define for the ROOT_PATH

    3) I set the below code for action script classes right, in this example Jesus. How do i set the Logger if i need to add logging in the main mxml file. Do i need to give the name of the main mxml file here.
    private static const logger:ILogger = LoggerFactory.getClassLogger(Jesus);

    Thanks,
    Saha

  • Saha

    Also where can i find the log files?

  • Ole Christian Langfjæran

    Hey there Saha!
    1) What kind of error do you get? Nullpointer?

    2) ROOT_PATH is the path to your backendserver which serves the AMF RemoteObject – in this case a Java BlazeDS. The example runs a Java jettyserver on http://localhost:8080/spring-logging-blazeds-war/
    and then you add the standard Java amf broker url like this: ROOT_PATH+”messagebroker/amf”
    I’m not to comfortable what you might use for php, .NET or something – but I’ll bet it’s something quite equal.

    3) Nope. You do not need to give reference to the main file.
    BUT the main.mxml file should start the logger e.g. create it. Like this is super:

    private static var loggerSetup:* = LoggerFactory.loggerFactory = new 
    RemoteLoggerFactory(ROOT_PATH+"messagebroker/amf");

    Sorry for the extremely late reply! Guess we must install a notifier of some sort on this blog…

    /OC