1  /**
     2   * com.sekati.log.Logger
     3   * @version 1.2.6
     4   * @author jason m horwitz | sekati.com
     5   * Copyright (C) 2007  jason m horwitz, Sekat LLC. All Rights Reserved.
     6   * Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
     7   */
     8  
     9  import com.sekati.events.Dispatcher;
    10  import com.sekati.log.Inspector;
    11  import com.sekati.log.LCBinding;
    12  import com.sekati.log.LogEvent;
    13  import com.sekati.time.StopWatch;
    14  
    15  /**
    16   * Logger is a multi-tiered debugging tool designed to clarify the debugging process.\
    17   * 
    18   * @TODO Add reflection to origin & clarify mode usage.
    19   * 
    20   * The Logger consists of two main tiered components:
    21   * 	- "levels" (method calls, i.e. 'trace', 'info', 'status', etc).
    22   * 	- "filters" (origin of the level call, i.e. '_root', 'myClassInstance', 'myMc', etc).
    23   * 	
    24   * New level (methods) may be created dynamically "overloading" the singleton class 
    25   * via {@link setLevel}, __resolve & proxy as illustrated in the example usage below.
    26   * NOTE: custom levels/overloading requires publishing >= Flash 9.
    27   * 
    28   * Logger can be configured to display its output in several different ways:
    29   * 	- Flash IDE Output Panel.
    30   * 	- {@link Console} hidden inside compiled swf.
    31   * 	- {@link Console} via <a href="http://debug.sekati.com">http://debug.sekati.com</a> localConnection.
    32   * 
    33   * @see {@link com.sekati.log.Console}
    34   */
    35  class com.sekati.log.Logger {
    36  
    37  	private static var _instance:Logger;
    38  	private var _levels:Object;
    39  	private var _filters:Array;
    40  	private var _proxyObj:Object;
    41  	private var _watch:StopWatch;
    42  	private var _logId:Number;
    43  	// output modes
    44  	private var _isEnabled:Boolean;
    45  	private var _isOutputLC:Boolean;
    46  	private var _isOutputSWF:Boolean;
    47  	private var _isOutputIDE:Boolean;	
    48  	// default level stubs
    49  	public var trace:Function;
    50  	public var info:Function;
    51  	public var notice:Function;	
    52  	public var status:Function;
    53  	public var warn:Function;
    54  	public var error:Function;
    55  	public var fatal:Function;
    56  
    57  	/**
    58  	 * Singleton Private Constructor
    59  	 */
    60  	private function Logger() {
    61  		resetLevels( );
    62  		resetFilters( );
    63  		_proxyObj = new Object( );
    64  		_watch = new StopWatch( true );
    65  		_logId = 0;
    66  		
    67  		_isEnabled = true;		
    68  		_isOutputLC = false;
    69  		_isOutputSWF = false;
    70  		_isOutputIDE = false;
    71  	}
    72  
    73  	/**
    74  	 * Singleton Accessor
    75  	 * @return Logger
    76  	 */	
    77  	public static function getInstance():Logger {
    78  		if (!_instance) _instance = new Logger( );
    79  		return _instance;
    80  	}
    81  
    82  	/**
    83  	 * Shorthand singleton accessor getter
    84  	 * @return Logger
    85  	 */
    86  	public static function get $():Logger {
    87  		return Logger.getInstance( );	
    88  	}
    89  
    90  	// Core Controllers
    91  	
    92  	/**
    93  	 * enabled setter
    94  	 * @param b (Boolean) enable or disable Logger
    95  	 */
    96  	public function set enabled(b:Boolean):Void {
    97  		_isEnabled = b;
    98  	}
    99  
   100  	/**
   101  	 * enabled getter
   102  	 * @return Boolean
   103  	 */	
   104  	public function get enabled():Boolean {
   105  		return _isEnabled;
   106  	}
   107  
   108  	/**
   109  	 * level and filter _status getter
   110  	 * @return String
   111  	 */
   112  	public function get _status():String {
   113  		return getLevels( ) + "\n" + getFilters( );
   114  	}
   115  
   116  	/**
   117  	 * reset Out to default levels and filters
   118  	 * @return Void
   119  	 */
   120  	public function reset():Void {
   121  		setAllLevels( true );
   122  		resetFilters( );
   123  	}
   124  
   125  	public function set isLC(b:Boolean):Void {
   126  		_isOutputLC = b;
   127  	}
   128  
   129  	public function set isSWF(b:Boolean):Void {
   130  		_isOutputSWF = b;
   131  	}
   132  
   133  	public function set isIDE(b:Boolean):Void {
   134  		_isOutputIDE = b;
   135  	}		
   136  
   137  	// Level Handlers
   138  
   139  	/**
   140  	 * Enable/disable a level and create the level if it does not already exist.
   141  	 * @param level (String) level name
   142  	 * @param isEnabled (Boolean) enabled status
   143  	 * @return Void
   144  	 */
   145  	public function setLevel(level:String, isEnabled:Boolean):Void {
   146  		_levels[level.toLowerCase( )] = isEnabled;
   147  	}
   148  
   149  	/**
   150  	 * Enable or disable all existing levels.
   151  	 * @param isEnabled (Boolean) enabled status
   152  	 * @return Void
   153  	 */
   154  	public function setAllLevels(isEnabled:Boolean):Void {
   155  		for (var i in _levels) {
   156  			setLevel( _levels[i], isEnabled );
   157  		}
   158  	}
   159  
   160  	/**
   161  	 * Reset all levels (clearing previously created levels)
   162  	 * @return Void
   163  	 */
   164  	public function resetLevels():Void {
   165  		_levels = {trace:true, info:true, status:true, warn:true, error:true, fatal:true, object:true};
   166  	}
   167  
   168  	/**
   169  	 * Returns a stringified overview of all levels statuses.
   170  	 * @return String
   171  	 */
   172  	public function getLevels():String {
   173  		var a:Array = new Array( );
   174  		for (var i in _levels) {
   175  			a.push( i + ":" + _levels[i].toString( ) );
   176  		}
   177  		return "_levels={" + a.toString( ) + "};";
   178  	}
   179  
   180  	/**
   181  	 * object is a special level (method) which handles object recursion via {@link com.sekati.log.Inspector}
   182  	 * @param origin (Object) origin for filtering purposes
   183  	 * @param obj (Object) object to be recursed thru Out
   184  	 * @return Void 
   185  	 */
   186  	public function object(origin:Object, obj:Object):Void {
   187  		var insp:Inspector = new Inspector( obj, origin );
   188  		_output( "OBJECT", origin, insp );
   189  	}	
   190  
   191  	// Filter Handlers
   192  	
   193  	/**
   194  	 * Enable/disable a filter and create the filter if it does not already exist.
   195  	 * @param origin (Object) object to filter on [usually a string]
   196  	 * @param isFiltered (Boolean)
   197  	 * @return Void
   198  	 */
   199  	public function setFilter(origin:Object, isFiltered:Boolean):Void {
   200  		if (isFiltered) {
   201  			filter( origin );
   202  		} else {
   203  			unfilter( origin );
   204  		}
   205  	}
   206  
   207  	/**
   208  	 * Returns a stringified overview of all filters statuses.
   209  	 * @return String
   210  	 */
   211  	public function getFilters():String {
   212  		return "_filters=[" + _filters.toString( ) + "];";
   213  	}
   214  
   215  	/**
   216  	 * Reset all filters (clearing previous filters)
   217  	 * @return Void
   218  	 */
   219  	public function resetFilters():Void {
   220  		_filters = [];
   221  	}
   222  
   223  	/**
   224  	 * Check if an origin's output is being filtered
   225  	 * @param origin (Object) to check.
   226  	 * @return Boolean
   227  	 */
   228  	public function isFiltered(origin:Object):Boolean {
   229  		var o:String = String( origin );
   230  		for (var i:Number = 0; i < _filters.length ; i++) {
   231  			if (_filters[i] == o) {
   232  				return true;
   233  			}
   234  		}
   235  		return false;
   236  	}	
   237  
   238  	/**
   239  	 * Add an origin to the filters array.
   240  	 * @param origin (Object) to be added.
   241  	 * @return Void
   242  	 */
   243  	private function filter(origin:Object):Void {
   244  		var o:String = String( origin );
   245  		if (!isFiltered( o )) {
   246  			_filters.push( o );
   247  		}
   248  	}
   249  
   250  	/**
   251  	 * Remove an origin from the filters array.
   252  	 * @param origin (Object) to be removed.
   253  	 * @return Void
   254  	 */
   255  	private function unfilter(origin:Object):Void {
   256  		var o:String = String( origin );
   257  		for (var i:Number = 0; i < _filters.length ; i++) {
   258  			if (_filters[i] == o) {
   259  				_filters.splice( i, 1 );
   260  				break;
   261  			}
   262  		}
   263  	}	
   264  
   265  	/*
   266  	// currently unused - as3 has getNameSpace - for now maybe better to keep filtering on object/string rather than class?
   267  	private function resolveOrigin ($origin) {
   268  	var o = (typeof ($origin) == "string") ? $origin : $origin._classname;
   269  	if (!o) {
   270  	o = $origin;
   271  	}
   272  	trace (o);
   273  	}
   274  	 */	
   275  	 
   276  	// Output Handlers
   277  	private function _output(level:String, origin:Object, msg:Object):Void {
   278  		// validate that we should be outputting this content: Logger enabled, level enabled, origin unfiltered & proper LogEvent.
   279  		if (_isEnabled == false || _levels[level] == false || isFiltered( origin ) == true || !(level.toLowerCase( ).indexOf( "__get__" ) <= -1)) {
   280  			return;
   281  		}
   282  		var benchmark:Number = _watch.lap( );
   283  		var id:Number = _logId++;
   284  		var e:LogEvent = new LogEvent( {id:id, type:level.toLowerCase( ), origin:String( origin ), message:String( msg ), benchmark:benchmark} );
   285  		
   286  		// dispatch event to localConnection Console
   287  		if(_isOutputLC) {
   288  			LCBinding.send( e );
   289  		}
   290  		// dispatch event to embedded Console (only if isOutputLC is disabled to avoid duplicate Console entries)
   291  		if(_isOutputSWF && !_isOutputLC) {
   292  			Dispatcher.$.dispatchEvent( e );
   293  		}		
   294  		// dispatch event to output panel
   295  		if(_isOutputIDE) {
   296  			trace( id +" " + level.toUpperCase( ) + "\t " + origin + "\t " + msg + "\t (" + benchmark + " ms)" );
   297  		}
   298  	}
   299  
   300  	public function setOutputMode(isLocal:Boolean, isRemote:Boolean, isIDE:Boolean):Void {	
   301  	}
   302  
   303  	// Level Overload	
   304  
   305  	// __resolve catches all wrapper & invented levels and processes them thru the proxy to _output: cool!
   306  	private function __resolve(name:String):Function {
   307  		if (name.indexOf( LogEvent.onLogEVENT ) > 1) {
   308  			//trace("!!!!!!!!! logger returning: "+name+" reflects: "+Stringifier.stringify(LogEvent));
   309  			return;
   310  		}
   311  		var f:Function = function():Object {
   312  			arguments.unshift( name );
   313  			return __proxy.apply( _proxyObj, arguments );
   314  		};
   315  		_proxyObj[name] = f;
   316  		return f;
   317  	}
   318  
   319  	private function __proxy(name:String):Void {
   320  		arguments.shift( );
   321  		var n:String = String( name ).toLowerCase( );
   322  		var o:String = String( arguments[0] );
   323  		var s:String = String( arguments[1] );
   324  		_instance._output( n, o, s );
   325  	}	
   326  
   327  	/**
   328  	 * Destroy the Singleton instance.
   329  	 * @return Void
   330  	 */
   331  	public function destroy():Void {
   332  		_watch.destroy( );
   333  		LCBinding.disconnect( );
   334  		for(var i in _instance) {
   335  			delete _instance[i];	
   336  		}
   337  		delete _instance;
   338  	}			
   339  }