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!