/** * com.sekati.log.Out * @version 1.9.1 * @author jason m horwitz | sekati.com * Copyright (C) 2007 jason m horwitz, Sekat LLC. All Rights Reserved. * Released under the MIT License: http://www.opensource.org/licenses/mit-license.php */ import com.sekati.log.OutPanel; import com.sekati.log.Inspector; import com.sekati.events.Dispatcher; import com.sekati.events.Event; import com.sekati.time.StopWatch; /** * Out is a multi-tiered trace replacement which uses debug levels and * object/instance filters to assist and clarify the debugging process. * New level method calls can be created dynamically "overloading" * the class via __resolve and proxy. * * {@code Usage: * var out:Out = Out.getInstance(); * out.createPanel(640,480); // optionally create an outPanel in the swf * out.trace(this,"hello world"); // simple hello world * out.setFilter(this,true); // filters this object from further output * out.info(this,"hello world"); // wont display due to filter * out.setFilter(this,false); // unfilter this object * out.setLevel("trace",false); // disable level trace * out.trace(this,"hello world"); // wont display due to disabled level * out.setLevel("debug",true); // creates a new level named "debug" & enable it * out.debug(this,"hello world"); // displays within the new debug level * out.object(this,{foo:5, bar:1}); // special object level does basic object recursion * } */ class com.sekati.log.Out { private static var _instance:Out; private static var _levels:Object = {TRACE:true, INFO:true, STATUS:true, WARN:true, ERROR:true, FATAL:true, OBJECT:true}; private static var _filters:Array = new Array( ); private static var _proxyObj:Object = new Object( ); private static var _isEnabled:Boolean = true; private static var _isPanel:Boolean = false; private static var _benchwatch:StopWatch = new StopWatch( false ); // default level stubs public var trace:Function; public var info:Function; public var status:Function; public var warn:Function; public var error:Function; public var fatal:Function; /** * Singleton Private Constructor */ private function Out() { } /** * Singleton Accessor * @return Out */ public static function getInstance():Out { if (!_instance) _instance = new Out( ); return _instance; } /** * shorthand singleton accessor getter */ public static function get $():Out { return Out.getInstance( ); } /** * wrapper to draw a panel to swf for output via {@link OutPanel} * @param w (Number) panel width * @param h (Number) panel height * @return Void */ public function createPanel(w:Number, h:Number):Void { _isPanel = true; OutPanel.getInstance( w, h ); } // Core controllers /** * Out enabled setter * @param b (Boolean) enable or disable Out */ public function set enabled(b:Boolean):Void { _isEnabled = b; } /** * Out enabled getter * @return Boolean */ public function get enabled():Boolean { return _isEnabled; } /** * level and filter _status getter * @return String */ public function get _status():String { return getLevels( ) + "\n" + getFilters( ); } /** * reset Out to default levels and filters * @return Void */ public function reset():Void { setAllLevels( true ); resetFilters( ); } // Level Handlers /** * set a levels enabled status or create a new level * @param level (String) level name * @param isEnabled (Boolean) * @return Void */ public function setLevel(level:String, isEnabled:Boolean):Void { _levels[level.toUpperCase( )] = isEnabled; } /** * set all levels status * @param isEnabled (Boolean) * @return Void */ public function setAllLevels(isEnabled:Boolean):Void { for (var i in _levels) { setLevel( _levels[i], isEnabled ); } } /** * returns a string of all levels for overview/status purposes * @return String */ public function getLevels():String { var a:Array = new Array( ); for (var i in _levels) { a.push( i + ":" + _levels[i].toString( ) ); } return "_levels={" + a.toString( ) + "};"; } // Object Filters /** * set a filters enabled status or create a new filter * @param origin (Object) object to set filter on * @param isFiltered (Boolean) * @return Void */ public function setFilter(origin:Object, isFiltered:Boolean):Void { if(isFiltered) { filter( origin ); } else { unfilter( origin ); } } /** * return a string of all filters for overview/status purposes * @return String */ public function getFilters():String { return "_filters=[" + _filters.toString( ) + "];"; } /** * reset all filters */ public function resetFilters():Void { _filters = []; } private function filter(origin:Object):Void { var o:String = String( origin ); if (!isFiltered( o )) { _filters.push( o ); } } private function unfilter(origin:Object):Void { var o:String = String( origin ); for (var i:Number = 0; i < _filters.length ; i++) { if (_filters[i] == o) { _filters.splice( i, 1 ); break; } } } public function isFiltered(origin:Object):Boolean { var o:String = String( origin ); for (var i:Number = 0; i < _filters.length ; i++) { if (_filters[i] == o) { return true; } } return false; } // currently unused - as3 has getNameSpace - for now maybe better to keep filtering on object/string rather than class? private function resolveOrigin($origin:Object):Void { var o:Object = (typeof ($origin) == "string") ? $origin : $origin._classname; if (!o) { o = $origin; } trace( o ); } // Output methods private function _output(level:String, origin:Object, msg:Object):Void { if (!_isEnabled || !_levels[level] || isFiltered( origin )) { return; } var o:String = level + ":[" + String( origin ) + "]: " + msg; if(!_isPanel) { trace( o ); } else { var e:Event = new Event( "OUT_EVENT", this, {message:o, level:level} ); Dispatcher.$.dispatchEvent( e ); } } // __resolve catches all wrapper & invented levels and processes them thru the proxy to _output: cool! private function __resolve(name:String):Function { if (name.indexOf( "OUT_EVENT" ) > 1) { return; } var f:Function = function ():Object { arguments.unshift( name ); return __proxy.apply( _proxyObj, arguments ); }; _proxyObj[name] = f; return f; } private function __proxy(name:String):Void { arguments.shift( ); var n:String = String( name ).toUpperCase( ); var o:String = String( arguments[0] ); var s:String = String( arguments[1] ); _instance._output( n, o, s ); } /** * object is a special level (method) which handles object recursion via {@link com.sekati.log.Inspector} * @param origin (Object) origin for filtering purposes * @param obj (Object) object to be recursed thru Out * @return Void */ public function object(origin:Object, obj:Object):Void { var insp:Inspector = new Inspector( obj, origin ); _output( "OBJECT", origin, insp ); } /** * benchmark is a special level (method) which handles benchmarking of call execution * @param origin (Object) origin for filtering purposes * @param msg (String) any extraneous name or message info you wish to output before the benchmark results * @param mode (String) valid modes are: "start", "stop", "lap" * @return Void * @throws Error on invalid 'mode' argument */ public function benchmark(origin:Object, msg:String, mode:String):Void { //trace("bench call"); /* if (mode.toLowerCase() != "start" && mode.toLowerCase() != "stop" && mode.toLowerCase() != "lap") { throw new Error ("@@@ com.sekati.log.Out Error: bench() expects mode param: 'start', 'stop' or 'lap'."); return; } */ var b:Number; switch (mode.toLowerCase( )) { case "start" : b = _benchwatch.start( ); break; case "stop" : b = _benchwatch.stop( ); break; case "lap" : b = _benchwatch.lap( ); break; default : throw new Error( "@@@ com.sekati.log.Out Error: bench() expects mode param: 'start', 'stop' or 'lap'." ); return; //break; } //var b:Number = _benchwatch[mode](); var bs:String = String( _benchwatch.read( ) ); var str:String = String( msg + " | benchmark (" + mode + "): " + bs + "ms" ); //trace(str); _output( "BENCHMARK", origin, str ); } }