In my last post I mentioned that my TabManager class took advantage of some of my other utility classes. The most commonly used of these are my logging classes which make debugging my code super simple. Over the years, these classes have grown from simple wrappers for the built in method “trace()” to robust event-driven models, and back down to easy-to-use, flexible implementations like the one I’m about to share with you.
Before I get started, let me first make it very clear that this implementation is totally ripped off from a few other sources, most notably, the awesome Flex Logger. I don’t use Flex a lot, and I honestly don’t really care for it, but there are still some very cool things it does. I don’t know how many times I’ve been reading through the AS3 API when I come across a fantastic object that I want to use only to find out that it’s from the infamous “mx” package. Curses! Alas, I’ll have to just settle for recreating all Flex’s cool mumbo-jumbo in pure AS3 myself.
Now, lets get talking about the logger. For those of you who don’t care about the inner workings and just want to use it, you can find the link to the source code and sample implementation FLA at the bottom of this post. For the rest of you (all two of you), here we go:
My logging system is built around three core components: the Log, the Loggers, and the Consoles. Lets talk about each in turn.
The heart of the logging system is a singleton class called “Log” (it rolls over your neighbor’s dog). For those who care, I used a slightly different singleton implementation on this class than on my TabManager, not for any particular reason, but just because I like to mix it up. The roll of the Log class is to handle all of the logging messages sent out from the various Loggers in all of your classes, do some simple error checking, and then pass those to the active Consoles. As a singleton, there’s only ever one instance of Log, which keeps things nice and organized.
package org.tomasino.logging
{
import flash.errors.IllegalOperationError;
public class Log
{
private static var _allowInstantiation:Boolean = false;
private static var _inst:Log;
public var logLevel:int = 0;
public var filters:Array = new Array();
private var consoles:Array = new Array ();
public function Log ()
{
if (!_allowInstantiation)
{
throw new IllegalOperationError ('Cannot instantiate Log. Use Log.inst.');
}
var traceConsole:TraceConsole = new TraceConsole ();
consoles.push (traceConsole);
}
public static function get inst():Log
{
if (_inst == null)
{
_allowInstantiation = true;
_inst = new Log();
_allowInstantiation = false;
}
return _inst;
}
public function log (category:String, level:Number, msg:String):void
{
if ((level >= logLevel) && (logLevel != -1))
{
var show:Boolean = true;
if (filters.length)
{
show = false;
for (var i:int = 0; i < filters.length; ++i)
{
if (filters[i] is RegExp)
{
var matchRegExp:RegExp = filters[i] as RegExp;
if (matchRegExp.test (category))
{
show = true;
break;
}
}
else if (filters[i] is String)
{
var matchString:String = filters[i] as String;
if (matchString == category.substr (0, matchString.length))
{
show = true;
break;
}
}
}
}
if (show)
{
for (i = 0; i < consoles.length; ++i)
{
var console:IConsole = consoles[i] as IConsole;
console.log (category, level, msg);
}
}
}
}
public function addConsole (c:IConsole):void
{
consoles.push (c);
}
}
}The “Logger” class is your workhorse. This is the class that gets instantiated in each class of your project. Each Logger gets passed either a string name or a reference to “this” in its constructor so it gains a particular identity. These identities are used by the various consoles to organize all of your debug information in a logical and pretty way that makes searching for errors a cinch. Logger has methods like info(), warn(), and error() that allow you to send messages to debugger with specific levels of importance. With some configuration of the Log class, you can filter out these messages by level, string or regular expression (how fancy!).
package org.tomasino.logging
{
import flash.errors.IllegalOperationError;
import flash.utils.getQualifiedClassName;
public class Logger
{
private var _category:String;
private var _log:Log;
public function Logger (category:Object):void
{
_log = Log.inst;
if (category is String)
{
_category = category as String;
}
else
{
_category = getQualifiedClassName (category);
}
}
public function log (level:int, message:String, ... rest):void
{
if (rest.length)
{
message += ' ' + rest.join (' ');
}
_log.log (_category, level, message);
}
public function debug (message:String, ... rest):void
{
if (rest.length)
{
message += ' ' + rest.join (' ');
}
log (LogLevel.DEBUG, message);
}
public function info (message:String, ... rest):void
{
if (rest.length)
{
message += ' ' + rest.join (' ');
}
log (LogLevel.INFO, message);
}
public function warn (message:String, ... rest):void
{
if (rest.length)
{
message += ' ' + rest.join (' ');
}
log (LogLevel.WARN, message);
}
public function error (message:String, ... rest):void
{
if (rest.length)
{
message += ' ' + rest.join (' ');
}
log (LogLevel.ERROR, message);
}
public function fatal (message:String, ... rest):void
{
if (rest.length)
{
message += ' ' + rest.join (' ');
}
log (LogLevel.FATAL, message);
}
}
}
Finally there are the Consoles. The Consoles are the classes that get the debug information and present it back to you in some way. By default, Log is set up to have the “TraceConsole” enabled automatically. TraceConsole is a wrapper around the AS3 trace() method that does some pretty formatting, and automatically hides your traces in live environments (unless you pass a particular query string parameter). The other Console I have included is “LogBookConsole”, which uses a LocalConnection to send your debug data to LogBook. All of the Consoles implement an interface called “IConsole”.
If you aren’t familiar with LogBook, it’s a really awesome AIR application built by Comcast for their own debugging needs that takes LocalConnection data, parses it, and displays it within a really pretty DataGrid. It also has some great searching and filtering options. I highly recommend it.
package org.tomasino.logging
{
public interface IConsole{
function log(category:String, level:Number, msg:String):void;
}
}
package org.tomasino.logging
{
public class TraceConsole implements IConsole
{
private var _lastcategory:String;
public function TraceConsole ():void { }
public function log (category:String, level:Number, message:String):void
{
var t:String = ''; // Define output string
if (category != _lastcategory)
{
t += (_lastcategory) ? '\n' : '';
t += '==> ' + category + '\n';
}
t += '\t';
switch (level)
{
case LogLevel.DEBUG:
t += 'DEBUG -- ';
break;
case LogLevel.INFO:
t += 'INFO -- ';
break;
case LogLevel.WARN:
t += 'WARN -- ';
break;
case LogLevel.ERROR:
t += 'ERROR -- ';
break;
case LogLevel.FATAL:
t += 'FATAL -- ';
break;
}
t += message;
_lastcategory = category;
if (t) trace(t);
}
}
}
package org.tomasino.logging
{
import flash.events.StatusEvent;
import flash.net.LocalConnection;
public class LogBookConsole implements IConsole{
public static const LOGGING_METHOD:String = "logMessage";
private var _lc:LocalConnection;
private var _connection:String;
public function LogBookConsole(connectionId:String)
{
_lc = new LocalConnection();
_lc.addEventListener(StatusEvent.STATUS, statusEventHandler);
_connection = connectionId;
}
private function statusEventHandler(event:StatusEvent):void
{
//trace("statusEventHandler: " + event.code);
}
public function log(category:String, level:Number, msg:String):void{
var d:Date = new Date();
try {
_lc.send(_connection, LOGGING_METHOD, d, category, level, msg);
} catch(error:Error) {
trace('ERROR - Cannot sconnect to local connection');
}
}
public function toString():String
{
return "LocalConnectionTarget[" + _connection + "]";
}
public function getConnectionId():String {
return _connection;
}
}
}
In a normal implementation, all that is necessary to use the basic logging system is to import Logger into one of your classes, instantiate and name it, and call one of its methods. This will use the standard TraceConsole and give you some pretty output. If you want a greater control of the system, import Log into your document class and manipulate the filters array (takes either strings or regular expressions), add or remove consoles from the consoles array, or change the default log level by accessing the singleton instance. For instance: Log.inst.consoles.push (new LogBookConsole(‘_com.tomasinoblog’)); // will enable a LogBook connection on the string _com.tomasinoblog. One small note: for some reason, LogBook likes connections that start with an underscore.
That’s all it takes. It compiles down pretty small, too, which I’m a fan of. Ready to try it out?
There’s no license on this or any of my utility code (anything in the com.tomasino packages). Feel free to use it or modify it at your own discretion. If you find something useful I’d love to know about it. Thanks.
Download the source and example FLA: here
Make sure to grab the latest code from my github repository.
Hi there,
just a quick comment on LogBook liking connections that start with an underscore.
It’s related to security sandbox. From adobe’s livedocs
“To avoid specifying the domain name in the send() method, but to indicate that the receiving and sending LocalConnection objects are not in the same domain, precede the connection name with an underscore (_), in both the connect() and send() calls.”
http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/net/LocalConnection.html
@goliatone – Thanks for the info. That’s really interesting. I wonder why Adobe went with that method instead of just making a boolean parameter in the send call to specify same-domain or not. Ahh, well… at least I know there’s a reason for the underscore now. Thanks for the comment!
[...] TAGS: None My programming posts have been moved to my new coding blog – Tomasino Labs. This specific post can be found here. [...]
ran across your logging system and got so pumped that I wrote a all Flash(AIR) version of LogBook to use with it
looking forward to using it with your logging system.
great site
Carl
[...] you’re looking for something more robust or more information, here’s some links for you:My Flash AS3 Logger – my much more robust debugging solution. Tracer.as – a full featured trace() [...]