© 2008 aut0poietic

Using The Flex 3 Logging Framework [Updated]

[Updated April 21, 2009]

Today I updated the Console and ExternalConsoleTarget class files for use with FireBug.

I have basically cleaned up these classes a bit, and created an .swc file for you to drop into your project — it’s just much cleaner this way. The .swc file also includes two “abstract” error classes LoggedError and FatalError to extend your error classes from that automatically wrap in the call to Console to log the error. I use these Error classes and the Console itself CONSTANTLY. I finally wrapped everything in an SWC file because the code never changes.

The new download link contains the swc, the source files, and one test project showing how to use them.

Download New Source: aut0poietic-logging-swc-and-source

[Original Article]
When I was working daily in ActionScript 2.0, I had a class I used constantly. I called it “console” and basically all it did was allow Flash’s native trace method to work in a browser. I carried this class around with me from AS 2, to AS 3, Flex 2 to Flex 3. It gained the ability to talk to firebug instead of a text area. It gained the ability to send email.

On a recent project, I was unable to use my own code — everything had to be new and shiny. One of the first things I did was start thinking of my beloved console class.

Flex 3 contains a Logging framework under the mx.logging.* and mx.logging.targets.* packages that is used internally by the Flex Framework. Using the existing logging package to view messages from within the framework is fairly easy:

var logTarget:TraceTarget = new TraceTarget();
logTarget.filters=["mx.rpc.*","mx.messaging.*"];

logTarget.level = LogEventLevel.ALL;

logTarget.includeDate = true;
logTarget.includeTime = true;
logTarget.includeCategory = true;
logTarget.includeLevel = true;

Log.addTarget(logTarget);

The code above (from the Flex 3 Language Reference) will send any message logged by the classes within the mx.rpc.* and mx.messaging.* packages to the debugger. Useful all on it’s own.

Using the Logging Framework in your own classes is just as easy, if a little wordy. Lets pretend we are working in a component we’re building, and the events have been wired for us:

import mx.logging.targets.*;
import mx.logging.*;

private var myLogger:ILogger;

private function onCreationComplete(event:Event)
{
     var logTarget:TraceTarget = new TraceTarget();
     logTarget.filters=[
                               "mx.rpc.*",
                               "mx.messaging.*",
                               "us.aut0poietic.*"
                            ];

     logTarget.level = LogEventLevel.ALL;

     logTarget.includeDate = true;
     logTarget.includeTime = true;
     logTarget.includeCategory = true;
     logTarget.includeLevel = true;

     Log.addTarget(logTarget);

     myLogger = Log.getLogger(
                       "us.aut0poietic.MyComponent"
                     );
}

private function onMyButtonClick(event:MouseEvent)
{
     mylogger.info(
                         "{0} {1}, {2}",
                         event.currentTarget,
                         event.localX,
                         event.localY
                        )
}

This code will trace the information from the info call to the debug console in Flex 3, and it should look something like this:

3/9/2006 18:58:07.610 [INFO] myButton 11, 24

Very useful! However, I missed the ease of use with my console class and the ability to display the debugging statements and errors in Firebug.

As usual, Adobe provided for this in their design. The key is to create your own log target class that extends one of the existing classes in the mx.logging.targets package — I used LineFormattedTarget — and override the logEvent method.

Sending information to Firebug is as simple as making a JavaScript call to console.method. In Flex, we just need to use External Interface to send the object/method calls to the browser for executing in JavaScript. The syntax looks like this:

ExternalInterface.call(
                         "console.info",
                         "I'm tracing this!"
                            ) ;

So the first step in my plan for logging domination was to create a logging target that traced both to the debugger, but also the the Firebug console. Here’s the full class:

package us.aut0poietic.core.logging
{
	import flash.external.ExternalInterface;

	import mx.logging.LogEvent;
	import mx.logging.targets.LineFormattedTarget;

	/**
	 * @author Jer
	 */
	public class ExternalConsoleTarget extends LineFormattedTarget
	{
		private var _levels:Array  ;				// array of level text labels.
		private var _consoleMethods:Array ;
		private var _debug:Boolean = true ;

		/**
		 * Target class for the Flex Logging API.
		 *
		 */
		public function ExternalConsoleTarget()
		{
			super();

			_levels = new Array() ;
			_levels[0] = "[ALL  ] " ;
			_levels[2] = "[DEBUG]" ;
			_levels[4] = "[INFO ]" ;
			_levels[6] = "[WARN ]" ;
			_levels[8] = "[ERROR]" ;
			_levels[1000] = "[FATAL]" ;

			_consoleMethods = new Array() ;
			_consoleMethods[0] = "console.log" ;
			_consoleMethods[2] = "console.debug" ;
			_consoleMethods[4] = "console.info" ;
			_consoleMethods[6] = "console.warn" ;
			_consoleMethods[8] = "console.error" ;
			_consoleMethods[1000] = "console.error" ;

		}
		/**
		 * Event handler that is called by the logging API -- not to be called directly.
		 * @param event
		 */
		public override function logEvent(event:LogEvent):void
		{
			if( !_debug && event.level < 8 )
			{
				return ;
			}

			traceStatement(event) ;
			writeToErrorLog(event) ;
		}
		/**
		 * traces the message to the Flex debugger, along with time/date (UTC) and the message level.
		 * @param event
		 *
		 */
		private function traceStatement(event:LogEvent):void
		{
			//trace(_levels[event.level].toUpperCase() + " " + event.message );
			trace(_levels[event.level].toUpperCase() + getTime() + " - " + event.message );
		}
		/**
		 * Writes the message to the log file, along with the date/time (UTC) and error level.
		 * @param event
		 *
		 */
		private function writeToErrorLog(event:LogEvent):void
		{

			ExternalInterface.call(_consoleMethods[event.level], _levels[event.level].toUpperCase() +
						     ": " + getTime() + " - " + event.message ) ;
		}
		/**
		 * gets the current UTC Time ;
		 * @return The date and time in UTC long format.
		 *
		 */
		private function getTime():String
		{
			var d:Date = new Date() ;
			return d.toUTCString()
		}
		public function get debugMode():Boolean
		{
			return _debug ;
		}
		public function set debugMode(value:Boolean):void
		{
			_debug = value ;
		}
	}
}

You could now supply this target to the Log class using addTarget() and our messages to myLogger.info() would be seen in firebug, with all the formatting and alerts that provides!

Thing is, I'm a lazy guy. I still find this more work than I want to do. So I rebuilt my console class (now as Console to maintain naming conventions in Flex 3). Console is a simple class that exposes all of the Flex/Firebug messaging levels for you to utilize. Here's the basic code:

////////////////////////////////////////////////////////////////////////////////
//
//  aut0poietic Console
//
//	Project: 		core-class
//	File Version:	1.3
//
//	Author: 		Jer / aut0poietic
//	Last Modified: 	04/21/09
//
////////////////////////////////////////////////////////////////////////////////
package
{
	import mx.logging.AbstractTarget;
	import mx.logging.ILogger;
	import mx.logging.Log;

	import us.aut0poietic.core.logging.ExternalConsoleTarget;
	/**
	 *  A TopLevel convenience class to be used to trace all output to the console and/or log file.
	 * @author Jer Brand
	 */
	public class Console extends Object
	{
		private static var _target:ExternalConsoleTarget = null ; 	// The Target object to handle all logging.
		private static var _log:ILogger ;
		private static var _additionalTargets:Object ;

		/**
		 * This class is static only -- calling the constructor will throw a fatal error.
		 */
		public function Console()
		{
			throw(new Error("The Console cannot be instanciated.")) ;
		}
		/**
		 * Semi-singleton implementation; instanciates any needed classes.
		 *
		 */
		private static function initTarget():void
		{
			if(_target == null)
			{
				_target = new ExternalConsoleTarget() ;
				Log.addTarget(_target);
				_log = Log.getLogger("us.aut0poietic") ;
			}
		}

		public static function setDebugMode(debug:Boolean):void
		{
			initTarget() ;
			_target.debugMode = debug ;
			if(debug) { info("DEBUG MODE ENABLED") ; }
		}

		/**
		 * Used to Log information to the console ;
		 * @param message Information to send to the console.
		 * @param additionalStatements additional parameters -- also sent to console.
		 * @example By convention info() is called in the following way
		 *
Console.info("EventType", "Message for the Log");
		 */
		public static function info(message:String, ... additionalStatements:Array):void
		{
			initTarget();
			message += additionalStatements.length == 0 ? "" : ", " + additionalStatements.join(", ") ;
			_log.info(message) ;
		}
		/**
		 * Used to Log information to the console.
		 * @param message Information to send to the console.
		 * @param additionalStatements additional parameters
		 * @example debug() can have as many parameters as you'd like:
		 *
Console.debug(this, "  is an", "example of ", this.className);
		 */
		public static function debug(message:String, ... additionalStatements):void
		{
			initTarget();
			message += additionalStatements.length == 0 ? "" : ", " + additionalStatements.join(", ") ;
			_log.debug(message)
		}
		/**
		 * Used to send a warning to the console
		 * @param message Information to send to the console and saved to the log.
		 * @param additionalStatements additional parameters -- also sent to console and log.
		 * warn() is intended to be used for situations where something within the application has gone wrong
		 * and there is the potential for an error to be raised.
		 * @example By convention warn() is called in the following way
		 *
Console.warn("EventType", "Message for the Log");
		 */
		public static function warn(message:String, ... additionalStatements):void
		{
			initTarget();
			message += additionalStatements.length == 0 ? "" : ", " + additionalStatements.join(", ") ;
			_log.warn(message)
		}
		/**
		 * Record an error to the console.
		 * @param message Information to send to the console and saved to the log.
		 * @param additionalStatements additional parameters -- also sent to console and log.
		 * error() is intended to be used for situations where something within the application gone seriously
		 * wrong. It is primarily used in Error classes, but can be utilized by the developer.
		 * @example By convention warn() is called in the following way
		 *
Console.error("EventType", "Message for the Log");
		 */
		public static function error(message:String, ... additionalStatements):void
		{
			initTarget();
			message += additionalStatements.length == 0 ? "" : ", " + additionalStatements.join(", ") ;
			_log.error(message)
		}
		/**
		 * Raise fatal error to the console.
		 * @param message Information to send to the console and saved to the log.
		 * @param additionalStatements additional parameters -- also sent to console and log.
		 * fatal() is intended to be used for situations where something within the application gone wrong
		 * and application execution will hault. It is primarily used in Error classes, but can be utilized
		 * by the developer
		 * @example By convention warn() is called in the following way
		 *
Console.fatal("EventType", "Message for the Log");
		 */
		public static function fatal(message:String, ... additionalStatements):void
		{
			initTarget();
			message += additionalStatements.length == 0 ? "" : ", " + additionalStatements.join(", ") ;
			_log.fatal(message)
		}

		public static function addSupplimentalLogTarget(targetClass:Class, id:String):void
		{
			if(_additionalTargets == null)
			{
				_additionalTargets = new Object() ;
			}
			if(_additionalTargets[id] == null)
			{
				var target:AbstractTarget = new targetClass() as AbstractTarget ;
				_additionalTargets[id] = target ;
				Log.addTarget(target);
			}
		}
	}
}

This should make my life much more simple. Now if I want to make a note about what a component is doing, or debug a value, I use:

Console.info("My Message Here") ;

And because Console is in the default package, I don't need to worry about scope or any of that other jazz.

The beauty of this is that you can also edit the the target class and have these messages written to file. Even better, you could create another LogTarget subclass and simply add it to the Log along with the ExternalConsoleTarget. Want to send only Error and Fatal to a database. Write yourself some PHP to insert the data on POST, and create your new target with a switch that only sends event.level >= ERROR via post to your PHP page.

I've provided the classes as a jumping off point below. Download, rip em apart, extend them -- have fun. If you do use them, let me know the post was useful in the comments and/or link to the code on your site. Likewise, if I have a typo, error or stupidity issue, let me know that too.

Download Source Files and .swc: aut0poietic-logging-swc-and-source

5 Comments

  1. Posted October 22, 2008 at 10:58 am | #

    Hey! It looks like someone got the website done. Congrats.

  2. Posted February 12, 2010 at 11:44 am | #

    You are a genius. Your classes work in Google Chrome’s Developer Tools panel straight out of the box.

  3. Posted February 12, 2010 at 1:05 pm | #

    Lee, awesome. Checking out your version of Console for Chrome (http://blog.lyraspace.com/2010/02/12/logging-flash-with-google-chrome-developer-tools-console/). Really, the only reason I have Firefox around now is because of Firebug. Thanks for the comment.

  4. Posted February 12, 2010 at 1:12 pm | #

    I need to put your class packages in … doing it now

  5. Posted February 12, 2010 at 1:17 pm | #

    Done. If you right-click and view source on the test app I did you can grab a SWC and I’ve included your classes as well … including your class packages … credit WELL DUE! Thanks dude. Made my day.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>