diff options
Diffstat (limited to 'framework')
| -rw-r--r-- | framework/Util/TLogRouter.php | 1101 | ||||
| -rw-r--r-- | framework/Util/TLogger.php | 88 | 
2 files changed, 1080 insertions, 109 deletions
diff --git a/framework/Util/TLogRouter.php b/framework/Util/TLogRouter.php index c944e17d..591bd2f6 100644 --- a/framework/Util/TLogRouter.php +++ b/framework/Util/TLogRouter.php @@ -3,8 +3,9 @@   * TLogRouter, TLogRoute, TFileLogRoute, TEmailLogRoute class file
   *
   * @author Qiang Xue <qiang.xue@gmail.com>
 + * @author Brad Anderson <javalizard@gmail.com>
   * @link http://www.pradosoft.com/
 - * @copyright Copyright © 2005-2008 PradoSoft
 + * @copyright Copyright © 2005-2010 PradoSoft
   * @license http://www.pradosoft.com/license/
   * @version $Id$
   * @package System.Util
 @@ -24,7 +25,7 @@ Prado::using('System.Data.TDbConnection');   * or an external configuration file specified by {@link setConfigFile ConfigFile}.
   * The format is as follows,
   * <code>
 - *   <route class="TFileLogRoute" Categories="System.Web.UI" Levels="Warning" />
 + *   <route class="TFileLogRoute" Categories="System.Web.UI" Levels="Warning" Roles="developer,administrator,other" Active="false" />
   *   <route class="TEmailLogRoute" Categories="Application" Levels="Fatal" Emails="admin@pradosoft.com" />
   * </code>
   * PHP configuration style:
 @@ -47,10 +48,20 @@ class TLogRouter extends TModule  	 */
  	private $_routes=array();
  	/**
 +	 * @var array list of routes needed to be logged before the page flush
 +	 */
 +	private $_preroutes=array();
 +	/**
  	 * @var string external configuration file
  	 */
  	private $_configFile=null;
 -
 +	/**
 +	 * @var boolean whether to do any routes
 +	 */
 +	private $_active=true;
 +	
 +	
 +	
  	/**
  	 * Initializes this module.
  	 * This method is required by the IModule interface.
 @@ -59,6 +70,8 @@ class TLogRouter extends TModule  	 */
  	public function init($config)
  	{
 +		parent::init($config);
 +		
  		if($this->_configFile!==null)
  		{
   			if(is_file($this->_configFile))
 @@ -79,6 +92,9 @@ class TLogRouter extends TModule  				throw new TConfigurationException('logrouter_configfile_invalid',$this->_configFile);
  		}
  		$this->loadConfig($config);
 +
 +		// This is needed for FirePhp because it outputs headers
 +		$this->getApplication()->attachEventHandler('onPreFlushOutput',array($this,'collectLogsPreFlush'));
  		$this->getApplication()->attachEventHandler('OnEndRequest',array($this,'collectLogs'));
  	}
 @@ -98,13 +114,23 @@ class TLogRouter extends TModule  					$properties = isset($route['properties'])?$route['properties']:array();
  					if(!isset($route['class']))
  						throw new TConfigurationException('logrouter_routeclass_required');
 +					if(isset($properties['disabled']) && $properties['disabled'])
 +						continue;
  					$route=Prado::createComponent($route['class']);
  					if(!($route instanceof TLogRoute))
  						throw new TConfigurationException('logrouter_routetype_invalid');
 -					foreach($properties as $name=>$value)
 -						$route->setSubproperty($name,$value);
 +					
  					$this->_routes[]=$route;
 -					$route->init($route);
 +					if($route instanceof IHeaderRoute)
 +						$this->_preroutes[]=$route;
 +					
 +					try {
 +						foreach($properties as $name=>$value)
 +							$route->setSubproperty($name,$value);
 +						$route->init($route);
 +					} catch(Exception $e) {
 +						$route->InitError = $e;
 +					}
  				}
  			}
  		}
 @@ -113,21 +139,38 @@ class TLogRouter extends TModule  			foreach($config->getElementsByTagName('route') as $routeConfig)
  			{
  				$properties=$routeConfig->getAttributes();
 +				if(($disabled=$properties->remove('disabled'))!==null)
 +					continue;
  				if(($class=$properties->remove('class'))===null)
  					throw new TConfigurationException('logrouter_routeclass_required');
  				$route=Prado::createComponent($class);
  				if(!($route instanceof TLogRoute))
  					throw new TConfigurationException('logrouter_routetype_invalid');
 -				foreach($properties as $name=>$value)
 -					$route->setSubproperty($name,$value);
 +					
  				$this->_routes[]=$route;
 -				$route->init($routeConfig);
 +				if($route instanceof IHeaderRoute)
 +					$this->_preroutes[]=$route;
 +				
 +				try {
 +					foreach($properties as $name=>$value)
 +						$route->setSubproperty($name,$value);
 +					$route->init($routeConfig);
 +				} catch(Exception $e) {
 +					$route->InitError = $e;
 +				}
  			}
  		}
  	}
 +	
 +	/**
 +	 * This returns the installed routes
 +	 * @return array of TLogRoute
 +	 */
 +	public function getRoutes() { return $this->_routes; }
  	/**
 -	 * Adds a TLogRoute instance to the log router.
 +	 * Adds a TLogRoute instance to the log router.  If a log route implements {@link IHeaderRoute}
 +	 * then it will get its log route data just before the page is written (b/c it needs that for the headers)
  	 * 
  	 * @param TLogRoute $route 
  	 * @throws TInvalidDataTypeException if the route object is invalid
 @@ -137,7 +180,9 @@ class TLogRouter extends TModule  		if(!($route instanceof TLogRoute))
  			throw new TInvalidDataTypeException('logrouter_routetype_invalid');
  		$this->_routes[]=$route;
 -		$route->init(null);
 +		if($route instanceof IHeaderRoute)
 +			$this->_preroutes[]=$route;
 +		$route->init($this);
  	}
  	/**
 @@ -149,6 +194,22 @@ class TLogRouter extends TModule  	}
  	/**
 +	 * @return boolean whether the TLogRouter is active or not.
 +	 */
 +	public function getActive()
 +	{
 +		return $this->_active;
 +	}
 +
 +	/**
 +	 * @param boolean tells the object whether it's active or not.
 +	 */
 +	public function setActive($v)
 +	{
 +		$this->_active = TPropertyValue::ensureBoolean($v);
 +	}
 +
 +	/**
  	 * @param string external configuration file in namespace format. The file
  	 * must be suffixed with '.xml'.
  	 * @throws TConfigurationException if the file is invalid.
 @@ -161,17 +222,50 @@ class TLogRouter extends TModule  	/**
  	 * Collects log messages from a logger.
 -	 * This method is an event handler to application's EndRequest event.
 +	 * This method is an event handler to the application's onPreFlush event.
 +	 * Only pre flush routes get this treatment.
 +	 * @param mixed event parameter
 +	 */
 +	public function collectLogsPreFlush($param) {
 +		if(!$this->_active) return;
 +		
 +		$logger=Prado::getLogger();
 +		foreach($this->_preroutes as $route)
 +			$route->collectLogs($logger);
 +	}
 +
 +	/**
 +	 * Collects log messages from a logger.
 +	 * This method is an event handler to the application's EndRequest event.
 +	 * Only post flush routes get this treatment.
  	 * @param mixed event parameter
  	 */
  	public function collectLogs($param)
  	{
 +		if(!$this->_active) return;
 +		
  		$logger=Prado::getLogger();
  		foreach($this->_routes as $route)
 -			$route->collectLogs($logger);
 +			if(!in_array($route, $this->_preroutes))
 +				$route->collectLogs($logger);
  	}
  }
 +
 +/**
 + * IHeaderRoute interface.
 + *
 + * This is used for registering log routers that output to the header so it can be routed before the page flush.
 + *
 + * @author Brad Anderson <javalizard@gmail.com>
 + * @version $Id$
 + * @package System.Util
 + * @since 3.0
 + */ 
 +
 +interface IHeaderRoute {
 +}
 +
  /**
   * TLogRoute class.
   *
 @@ -221,6 +315,14 @@ abstract class TLogRoute extends TApplicationComponent  		'fatal'=>TLogger::FATAL
  	);
  	/**
 +	 * @var string the id of the route
 +	 */
 +	private $_id=null;
 +	/**
 +	 * @var string the name of the route
 +	 */
 +	private $_name=null;
 +	/**
  	 * @var integer log level filter (bits)
  	 */
  	private $_levels=null;
 @@ -228,13 +330,180 @@ abstract class TLogRoute extends TApplicationComponent  	 * @var array log category filter
  	 */
  	private $_categories=null;
 +	/**
 +	 * @var array log controls filter
 +	 */
 +	private $_controls=null;
 +	/**
 +	 * @var array role filter
 +	 */
 +	private $_roles=null;
  	/**
 +	 * @var int|string the reference to the hit metadata.  This is a transient property per page hit.
 +	 */
 +	private $_metaid=null;
 +	/**
 +	 * @var int the user id of the hit.  This is a transient property per page hit.
 +	 */
 +	private $_userid=null;
 +	
 +	/**
 +	 * @var boolean whether this is an active route or not
 +	 */
 +	private $_active=true;
 +	/**
 +	 * $var Exception any problems on the loading of the module
 +	 */
 +	private $_error=null;
 +	/**
  	 * Initializes the route.
  	 * @param TXmlElement configurations specified in {@link TLogRouter}.
  	 */
  	public function init($config)
  	{
 +		if(is_array($config)) {
 +			if(isset($config['id']))
 +				$this->_id = $config['id'];
 +			if(isset($config['name']))
 +				$this->Name = $config['name'];
 +			if(isset($config['active']))
 +				$this->Active = $config['active'];
 +			if(isset($config['roles']))
 +				$this->Roles = $config['roles'];
 +			if(isset($config['categories']))
 +				$this->Categories = $config['categories'];
 +			if(isset($config['levels']))
 +				$this->Levels = $config['levels'];
 +			if(isset($config['controls']))
 +				$this->Controls = $config['controls'];
 +		}
 +	}
 +	
 +
 +	/**
 +	 * @return string the id of the route
 +	 */
 +	public function getId()
 +	{
 +		return $this->_id;
 +	}
 +
 +	/**
 +	 * @param The id of the route.
 +	 */
 +	public function setId($id)
 +	{
 +		$this->_id = $id;
 +	}
 +
 +	/**
 +	 * @return string the name of the route
 +	 */
 +	public function getName()
 +	{
 +		return $this->_name;
 +	}
 +
 +	/**
 +	 * @param The name of the route.
 +	 */
 +	public function setName($name)
 +	{
 +		$this->_name = $name;
 +	}
 +
 +	/**
 +	 * @return boolean true if the route is active
 +	 */
 +	public function getActive()
 +	{
 +		return $this->_active;
 +	}
 +
 +	/**
 +	 * @param boolean sets the object to active or not.
 +	 */
 +	public function setActive($v)
 +	{
 +		$this->_active = TPropertyValue::ensureBoolean($v);
 +	}
 +
 +	/**
 +	 * @return Exception this returns any errors the log route has
 +	 */
 +	public function getInitError()
 +	{
 +		return $this->_error;
 +	}
 +
 +	/**
 +	 * @param mixed this sets the errors that the log route may have
 +	 */
 +	public function setInitError($v)
 +	{
 +		$this->_error = $v;
 +	}
 +
 +	/**
 +	 * @return string this returns the meta data id associated with the route
 +	 */
 +	public function getMetaId()
 +	{
 +		return $this->_metaid;
 +	}
 +
 +	/**
 +	 * @param string this sets the meta data id associated with the route.
 +	 */
 +	public function setMetaId($v)
 +	{
 +		$this->_metaid = $v;
 +	}
 +
 +
 +	/**
 +	 * @return string this returns the user id associated with the route.
 +	 */
 +	public function getUserId()
 +	{
 +		return $this->_userid;
 +	}
 +
 +	/**
 +	 * @return string this sets the user id associated with the route.
 +	 */
 +	public function setUserId($v)
 +	{
 +		$this->_userid = $v;
 +	}
 +
 +	/**
 +	 * @return array log roles filter
 +	 */
 +	public function getRoles()
 +	{
 +		return $this->_roles;
 +	}
 +
 +	/**
 +	 * @param array|string The roles that this log router is attached to.
 +	 */
 +	public function setRoles($roles)
 +	{
 +		if(is_array($roles))
 +			$this->_roles=$roles;
 +		else
 +		{
 +			$this->_roles=array();
 +			$roles=strtolower($roles);
 +			foreach(explode(',',$roles) as $role)
 +			{
 +				$role=trim($role);
 +				if(!in_array($role, $this->_roles))
 +					$this->_roles[] = $role;
 +			}
 +		}
  	}
  	/**
 @@ -257,8 +526,10 @@ abstract class TLogRoute extends TApplicationComponent  		else
  		{
  			$this->_levels=null;
 -			$levels=strtolower($levels);
 -			foreach(explode(',',$levels) as $level)
 +			if(is_string($levels))
 +				$levels = explode(',',strtolower($levels));
 +			
 +			foreach($levels as $level)
  			{
  				$level=trim($level);
  				if(isset(self::$_levelValues[$level]))
 @@ -285,7 +556,7 @@ abstract class TLogRoute extends TApplicationComponent  			$this->_categories=$categories;
  		else
  		{
 -			$this->_categories=null;
 +			$this->_categories=array();
  			foreach(explode(',',$categories) as $category)
  			{
  				if(($category=trim($category))!=='')
 @@ -295,6 +566,33 @@ abstract class TLogRoute extends TApplicationComponent  	}
  	/**
 +	 * @return array list of controls to be looked for
 +	 */
 +	public function getControls()
 +	{
 +		return $this->_controls;
 +	}
 +
 +	/**
 +	 * @param array|string list of controls to be looked for. If the value is a string,
 +	 * it is assumed to be comma-separated control client ids.
 +	 */
 +	public function setControls($controls)
 +	{
 +		if(is_array($controls))
 +			$this->_controls=$controls;
 +		else
 +		{
 +			$this->_controls=array();
 +			foreach(explode(',',$controls) as $control)
 +			{
 +				if(($control=trim($control))!=='')
 +					$this->_controls[]=$control;
 +			}
 +		}
 +	}
 +
 +	/**
  	 * @param integer level value
  	 * @return string level name
  	 */
 @@ -320,9 +618,11 @@ abstract class TLogRoute extends TApplicationComponent  	 * @param integer timestamp
  	 * @return string formatted message
  	 */
 -	protected function formatLogMessage($message,$level,$category,$time)
 +	protected function formatLogMessage($message,$level,$category,$time, $memory)
  	{
 -		return @gmdate('M d H:i:s',$time).' ['.$this->getLevelName($level).'] ['.$category.'] '.$message."\n";
 +		if(!$this->MetaId)
 +			$this->MetaId = $this->Request->UserHostAddress;
 +		return '[metaid: ' .$this->MetaId.'] ' . @date('M d H:i:s',$time).' [Memory: '.$memory.'] ['.$this->getLevelName($level).'] ['.$category.'] '.$message."\n";
  	}
  	/**
 @@ -331,10 +631,83 @@ abstract class TLogRoute extends TApplicationComponent  	 */
  	public function collectLogs(TLogger $logger)
  	{
 -		$logs=$logger->getLogs($this->getLevels(),$this->getCategories());
 +		// if not active or roles don't match, end function
 +		if(!$this->_active || ($this->_roles && !array_intersect($this->_roles, $this->User->Roles))) return;
 +		
 +		Prado::trace('Routing Logs: '.get_class($this) . '->id='.$this->id,'System.Util.TLogRouter');
 +		
 +		$logs=$logger->getLogs($this->getLevels(),$this->getCategories(),$this->getControls());
  		if(!empty($logs))
  			$this->processLogs($logs);
  	}
 +	
 +	/**
 +	 *	@return string this is the xml representation of the route
 +	 */
 +	public function toXml() {
 +		$xml = '<route ' . $this->encodeId() . $this->encodeName() . $this->encodeClass() . $this->encodeLevels() . 
 +			$this->encodeCategories() . $this->encodeRoles() . $this->encodeControls() . '/>';
 +		return $xml;
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the id of the route as an xml attribute
 +	 */
 +	protected function encodeId() {
 +		return 'id="'. $this->_id .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the name of the route as an xml attribute
 +	 */
 +	protected function encodeName() {
 +		$active = '';
 +		if(!$this->_active) $active = 'active="'. ($this->_active?'true':'false') .'" ';
 +		return 'name="'. $this->_name .'" ' . $active;
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the class of the route as an xml attribute
 +	 */
 +	protected function encodeClass() {
 +		return 'class="'. get_class($this) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the levels of the route as an xml attribute
 +	 */
 +	protected function encodeLevels() {
 +		if(!$this->_levels) return '';
 +		$levels = array();
 +		foreach(self::$_levelNames as $level => $name)
 +			if($level & $this->_levels)
 +				$levels[] = strtolower($name);
 +		return 'levels="'. implode(',', $levels) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the categories of the route as an xml attribute
 +	 */
 +	protected function encodeCategories() {
 +		if(!$this->_categories) return '';
 +		return 'categories="'. implode(',', $this->_categories) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the roles of the route as an xml attribute
 +	 */
 +	protected function encodeRoles() {
 +		if(!$this->_roles) return '';
 +		return 'roles="'. implode(',', $this->_roles) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the controls of the route as an xml attribute
 +	 */
 +	protected function encodeControls() {
 +		if(!$this->_roles) return '';
 +		return 'controls="'. implode(',', $this->_controls) .'" ';
 +	}
  	/**
  	 * Processes log messages and sends them to specific destination.
 @@ -345,7 +718,9 @@ abstract class TLogRoute extends TApplicationComponent  	 *   [0] => message
  	 *   [1] => level
  	 *   [2] => category
 -	 *   [3] => timestamp);
 +	 *   [3] => timestamp
 +	 *   [4] => memory in bytes
 +	 *   [5] => control);
  	 */
  	abstract protected function processLogs($logs);
  }
 @@ -382,11 +757,80 @@ class TFileLogRoute extends TLogRoute  	 */
  	private $_logPath=null;
  	/**
 +	 * @var string original directory set for the log files so it can be recreated
 +	 */
 +	private $_logPradoPath=null;
 +	/**
  	 * @var string log file name
  	 */
  	private $_logFile='prado.log';
  	/**
 +	 * Initializes the route.
 +	 * @param TXmlElement configurations specified in {@link TLogRouter}.
 +	 * @throws TConfigurationException if {@link getSentFrom SentFrom} is empty and
 +	 * 'sendmail_from' in php.ini is also empty.
 +	 */
 +	public function init($config)
 +	{
 +		parent::init($config);
 +		
 +		if(is_array($config)) {
 +			if(isset($config['logfile']))
 +				$this->LogFile = $config['logfile'];
 +			if(isset($config['logpath']))
 +				$this->LogPath = $config['logpath'];
 +			if(isset($config['maxfilesize']))
 +				$this->MaxFileSize = $config['maxfilesize'];
 +			if(isset($config['maxfilesize']))
 +				$this->MaxLogFiles = $config['maxlogfiles'];
 +		}
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the TFileLogRoute as a string
 +	 */
 +	public function toXml() {
 +		$xml = '<route ' .$this->encodeId(). $this->encodeName().$this->encodeClass() . $this->encodeLevels() . 
 +			$this->encodeCategories() . $this->encodeControls() . $this->encodeRoles() . $this->encodeMaxFileSize(). 
 +			$this->encodeMaxLogFiles(). $this->encodeLogPath().$this->encodeLogFile().'/>';
 +		return $xml;
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the maxfilesize of the route as an xml attribute
 +	 */
 +	protected function encodeMaxFileSize() {
 +		if(!$this->MaxFileSize) return '';
 +		return 'maxfilesize="'. addslashes($this->MaxFileSize) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the maxlogfiles of the route as an xml attribute
 +	 */
 +	protected function encodeMaxLogFiles() {
 +		if(!$this->MaxFileSize) return '';
 +		return 'maxlogfiles="'. addslashes($this->MaxLogFiles) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the logpath of the route as an xml attribute
 +	 */
 +	protected function encodeLogPath() {
 +		if(!$this->LogPath) return '';
 +		return 'logpath="'. addslashes($this->_logPradoPath) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the logfile of the route as an xml attribute
 +	 */
 +	protected function encodeLogFile() {
 +		if(!$this->LogFile) return '';
 +		return 'logfile="'. addslashes($this->LogFile) .'" ';
 +	}
 +	
 +	
 +	/**
  	 * @return string directory storing log files. Defaults to application runtime path.
  	 */
  	public function getLogPath()
 @@ -402,6 +846,7 @@ class TFileLogRoute extends TLogRoute  	 */
  	public function setLogPath($value)
  	{
 +		$this->_logPradoPath = $value;
  		if(($this->_logPath=Prado::getPathOfNamespace($value))===null || !is_dir($this->_logPath) || !is_writable($this->_logPath))
  			throw new TConfigurationException('filelogroute_logpath_invalid',$value);
  	}
 @@ -469,7 +914,7 @@ class TFileLogRoute extends TLogRoute  		if(@filesize($logFile)>$this->_maxFileSize*1024)
  			$this->rotateFiles();
  		foreach($logs as $log)
 -			error_log($this->formatLogMessage($log[0],$log[1],$log[2],$log[3]),3,$logFile);
 +			error_log($this->formatLogMessage($log[0],$log[1],$log[2],$log[3],$log[4]),3,$logFile);
  	}
  	/**
 @@ -538,11 +983,65 @@ class TEmailLogRoute extends TLogRoute  	 */
  	public function init($config)
  	{
 +		parent::init($config);
 +		
 +		if(is_array($config)) {
 +			if(isset($config['emails']))
 +				$this->Emails = $config['emails'];
 +			if(isset($config['subject']))
 +				$this->Subject = $config['subject'];
 +			if(isset($config['from']))
 +				$this->SentFrom = $config['from'];
 +		}
 +		
  		if($this->_from==='')
  			$this->_from=ini_get('sendmail_from');
  		if($this->_from==='')
  			throw new TConfigurationException('emaillogroute_sentfrom_required');
  	}
 +	
 +	/**
 +	 *	@return string this encodes the TEmailLogRoute as an xml element
 +	 */
 +	public function toXml() {
 +		$xml = '<route ' .$this->encodeId(). $this->encodeName().$this->encodeClass() . $this->encodeLevels() . 
 +			$this->encodeCategories() . $this->encodeControls() . $this->encodeRoles() . $this->encodeEmails(). 
 +			$this->encodeSubject(). $this->encodeFrom().'/>';
 +		return $xml;
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the emails of the route as an xml attribute
 +	 */
 +	protected function encodeEmails() {
 +		if(!$this->Emails) return '';
 +		return 'emails="'. addslashes(implode(',',$this->Emails)) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the subject of the route as an xml attribute
 +	 */
 +	protected function encodeSubject() {
 +		if($this->Subject == self::DEFAULT_SUBJECT) return '';
 +		return 'subject="'. addslashes($this->Subject) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the from email of the route as an xml attribute
 +	 */
 +	protected function encodeFrom() {
 +		return 'sentfrom="'. addslashes($this->SentFrom) .'" ';
 +	}
 +	
 +	
 +	/**
 +	 *	This sends a test email with a test log message
 +	 */
 +	public function sendTestEmail() {
 +		$this->processLogs(array(
 +				array('Test Message',TLogger::DEBUG,'System.Util.TEmailLogRoute',microtime(true),memory_get_usage())
 +			));
 +	}
  	/**
  	 * Sends log messages to specified email addresses.
 @@ -552,7 +1051,7 @@ class TEmailLogRoute extends TLogRoute  	{
  		$message='';
  		foreach($logs as $log)
 -			$message.=$this->formatLogMessage($log[0],$log[1],$log[2],$log[3]);
 +			$message.=$this->formatLogMessage($log[0],$log[1],$log[2],$log[3],$log[4]);
  		$message=wordwrap($message,70);
  		$returnPath = ini_get('sendmail_path') ? "Return-Path:{$this->_from}\r\n" : '';
  		foreach($this->_emails as $email)
 @@ -603,7 +1102,7 @@ class TEmailLogRoute extends TLogRoute  	 */
  	public function setSubject($value)
  	{
 -		$this->_subject=$value;
 +		$this->_subject=$value ? $value : null;
  	}
  	/**
 @@ -638,29 +1137,198 @@ class TBrowserLogRoute extends TLogRoute  	/**
  	 * @var string css class for indentifying the table structure in the dom tree
  	 */
 -	private $_cssClass=null;
 +	private $_cssClass='log-route-browser';
 +
 +	/**
 +	 * Sends log messages to the browser.
 +	 * This does quartile analysis on the logs to highlight the memory and time offenders
 +	 * @param array list of log messages
 +	 */
  	public function processLogs($logs)
  	{
  		if(empty($logs) || $this->getApplication()->getMode()==='Performance') return;
  		$first = $logs[0][3];
 +		$mem_first = $logs[0][4];
  		$even = true;
 +		$use_interquartile_top_bottom = false;
  		$response = $this->getApplication()->getResponse();
 -		$response->write($this->renderHeader());
 -		for($i=0,$n=count($logs);$i<$n;++$i)
 +		
 +		$c = count($logs);
 +		for($i=0,$n=count($logs); $i<$n; $i++) {
 +			$logs[$i]['i'] = $i;
 +			if($i > 1 && $i < $n-1) {
 +				$logs[$i]['time_delta'] = $logs[$i+1][3] - $logs[$i][3];
 +				$logs[$i]['time_total'] = $logs[$i+1][3] - $first;
 +				$logs[$i]['mem_delta'] = $logs[$i+1][4] - $logs[$i][4];
 +				$logs[$i]['mem_total'] = $logs[$i+1][4] - $mem_first;
 +			} else {
 +				$logs[$i]['time_delta'] = '?';
 +				$logs[$i]['time_total'] = $logs[$i][3] - $first;
 +				$logs[$i]['mem_delta'] = '?';
 +				$logs[$i]['mem_total'] = $logs[$i][4] - $mem_first;
 +			}
 +			$logs[$i]['even'] = !($even = !$even);
 +		}
 +		
  		{
 -			if ($i<$n-1)
 -			{
 -				$timing['delta'] = $logs[$i+1][3] - $logs[$i][3];
 -				$timing['total'] = $logs[$i+1][3] - $first;
 +			vrsort($logs, 'mem_delta');
 +			$median = $logs[round($c/2)];
 +			$q1 = $logs[round($c/4)];
 +			$q3 = $logs[round($c*3/4)];
 +			
 +			$mem_delta_median = $median['mem_delta'];
 +			$mem_delta_q1 = $q1['mem_delta'];
 +			$mem_delta_q3 = $q3['mem_delta'];
 +			$irq = $mem_delta_q1 - $mem_delta_q3;
 +			
 +			if($use_interquartile_top_bottom) {
 +				$top = $mem_delta_q1 + $irq * 1.5;
 +				$bottom = $mem_delta_q3 - $irq * 1.5;
 +			} else {
 +				$top = $logs[round($c*2/100)]['mem_delta'];
 +				$bottom = $logs[round($c*98/100)]['mem_delta'];
  			}
 -			else
 -			{
 -				$timing['delta'] = '?';
 -				$timing['total'] = $logs[$i][3] - $first;
 +			
 +			$sum_top = 0;
 +			$sum_bottom = 0;
 +			$top_value = $mem_delta_q1;
 +			$bottom_value = $mem_delta_q3;
 +			
 +			$top_outliers = 0;
 +			$bottom_outliers = 0;
 +			for($i=0,$n=count($logs); $i<$n; $i++) {
 +				$logs[$i]['mem_delta_order'] = $i;
 +				$logs[$i]['top_outlier'] = false;
 +				$logs[$i]['bottom_outlier'] = false;
 +				if($logs[$i]['mem_delta'] > $top) {
 +					$logs[$i]['top_outlier'] = true;
 +					$top_outliers++;
 +					$sum_top += $logs[$i]['mem_delta'];
 +				}
 +				if($logs[$i]['mem_delta'] < $bottom) {
 +					$logs[$i]['bottom_outlier'] = true;
 +					$bottom_outliers++;
 +					$sum_bottom += $logs[$i]['mem_delta'];
 +				}
 +					
 +				if($logs[$i]['mem_delta'] > $mem_delta_q1) {
 +					$logs[$i]['mem_delta_quartile'] = 0;
 +					if($logs[$i]['mem_delta'] > $top_value)
 +						$top_value = $logs[$i]['mem_delta'];
 +				} else if($logs[$i]['mem_delta'] > $mem_delta_median) {
 +					$logs[$i]['mem_delta_quartile'] = 1;
 +				} else if($logs[$i]['mem_delta'] > $mem_delta_q3) {
 +					$logs[$i]['mem_delta_quartile'] = 2;
 +				} else {
 +					$logs[$i]['mem_delta_quartile'] = 3;
 +					if($logs[$i]['mem_delta'] < $bottom_value)
 +						$bottom_value = $logs[$i]['mem_delta'];
 +				}
 +			}
 +			if($top_outliers)
 +				$sum_top /= $top_outliers;
 +			if($bottom_outliers)
 +				$sum_bottom /= $bottom_outliers;
 +		}
 +		
 +		$metrics = array(
 +				'mem_outliers' => $top_outliers + $bottom_outliers, 
 +				'mem_top_outliers' => $top_outliers,
 +				'mem_bottom_outliers' => $bottom_outliers,
 +				'mem_avg_top_outliers' => round($sum_top),
 +				'mem_avg_bottom_outliers' => round($sum_bottom),
 +				'mem_median' => $mem_delta_median,
 +				'mem_q1' => $mem_delta_q1,
 +				'mem_q3' => $mem_delta_q3,
 +				'mem_top_irq' => $top,
 +				'mem_bottom_irq' => $bottom,
 +				'mem_top' => $top_value,
 +				'mem_bottom' => $bottom_value
 +			);
 +			
 +		{
 +			vrsort($logs, 'time_delta');
 +			$median = $logs[round($c/2)];
 +			$q1 = $logs[round($c/4)];
 +			$q3 = $logs[round($c*3/4)];
 +			
 +			$time_delta_median = $median['time_delta'];
 +			$time_delta_q1 = $q1['time_delta'];
 +			$time_delta_q3 = $q3['time_delta'];
 +			$irq = $time_delta_q1 - $time_delta_q3;
 +			
 +			if($use_interquartile_top_bottom) {
 +				$top = $time_delta_q1 + $irq * 1.5;
 +				$bottom = $time_delta_q3 - $irq * 1.5;
 +			} else {
 +				$top = $logs[round($c*2/100)]['time_delta'];
 +				$bottom = $logs[round($c*98/100)]['time_delta'];
 +			}
 +			
 +			$sum_top = 0;
 +			$sum_bottom = 0;
 +			$top_value = $time_delta_q1;
 +			$bottom_value = $time_delta_q3;
 +			
 +			$top_outliers = 0;
 +			$bottom_outliers = 0;
 +			for($i=0,$n=count($logs); $i<$n; $i++) {
 +				$logs[$i]['time_delta_order'] = $i;
 +				$logs[$i]['time_top_outlier'] = false;
 +				$logs[$i]['time_bottom_outlier'] = false;
 +				if($logs[$i]['time_delta'] > $top) {
 +					$logs[$i]['time_top_outlier'] = true;
 +					$top_outliers++;
 +					$sum_top += $logs[$i]['time_delta'];
 +				}
 +				if($logs[$i]['time_delta'] < $bottom) {
 +					$logs[$i]['time_bottom_outlier'] = true;
 +					$bottom_outliers++;
 +					$sum_bottom += $logs[$i]['time_delta'];
 +				}
 +					
 +				if($logs[$i]['time_delta'] > $time_delta_q1) {
 +					$logs[$i]['time_delta_quartile'] = 0;
 +					if($logs[$i]['time_delta'] > $top_value)
 +						$top_value = $logs[$i]['time_delta'];
 +				} else if($logs[$i]['time_delta'] > $time_delta_median) {
 +					$logs[$i]['time_delta_quartile'] = 1;
 +				} else if($logs[$i]['time_delta'] > $time_delta_q3) {
 +					$logs[$i]['time_delta_quartile'] = 2;
 +				} else {
 +					$logs[$i]['time_delta_quartile'] = 3;
 +					if($logs[$i]['time_delta'] < $bottom_value)
 +						$bottom_value = $logs[$i]['time_delta'];
 +				}
  			}
 -			$timing['even'] = !($even = !$even);
 -			$response->write($this->renderMessage($logs[$i],$timing));
 +			if($top_outliers)
 +				$sum_top /= $top_outliers;
 +			if($bottom_outliers)
 +				$sum_bottom /= $bottom_outliers;
 +		}
 +		$metrics += array(
 +				'time_outliers' => round(($top_outliers + $bottom_outliers) * 1000, 3), 
 +				'time_top_outliers' => $top_outliers,
 +				'time_bottom_outliers' => $bottom_outliers,
 +				'time_avg_top_outliers' => round($sum_top * 1000, 3),
 +				'time_avg_bottom_outliers' => round($sum_bottom * 1000, 3),
 +				'time_median' => round($time_delta_median * 1000, 3),
 +				'time_q1' => round($time_delta_q1 * 1000, 3),
 +				'time_q3' => round($time_delta_q3 * 1000, 3),
 +				'time_top_irq' => round($top * 1000, 3),
 +				'time_bottom_irq' => round($bottom * 1000, 3),
 +				'time_top' => round($top_value * 1000, 3),
 +				'time_bottom' => round($bottom_value * 1000, 3)
 +			);
 +		
 +		vsort($logs, 'i');
 +		//ksort($logs);
 +		
 +		$response->write($this->renderHeader($metrics));
 +		for($i=0,$n=count($logs);$i<$n;++$i)
 +		{
 +			$response->write($this->renderMessage($logs[$i]));
  		}
  		$response->write($this->renderFooter());
  	}
 @@ -681,7 +1349,7 @@ class TBrowserLogRoute extends TLogRoute  		return TPropertyValue::ensureString($this->_cssClass);
  	}
 -	protected function renderHeader()
 +	protected function renderHeader($metrics)
  	{
  		$string = '';
  		if($className=$this->getCssClass())
 @@ -689,64 +1357,112 @@ class TBrowserLogRoute extends TLogRoute  			$string = <<<EOD
  <table class="$className">
  	<tr class="header">
 -		<th colspan="5">
 +		<th colspan="7">
  			Application Log
  		</th>
  	</tr><tr class="description">
  	    <th> </th>
 -		<th>Category</th><th>Message</th><th>Time Spent (s)</th><th>Cumulated Time Spent (s)</th>
 +		<th>Category</th><th>Message</th>
 +		<th>Time Spent (s)</th>
 +		<th>Cumulated Time Spent (s)</th>
 +		<th>Δ Memory</th>
 +		<th>Memory</th>
  	</tr>
  EOD;
  		}
  		else
  		{
 +			$top_outliers = 'unset';
 +			if($metrics['mem_top_outliers'])
 +				$top_outliers = 'Avg Upper Outlier: '. $metrics['mem_avg_top_outliers'] . '   ';
 +			if($metrics['time_top_outliers'])
 +				$time_top_outliers = 'Avg Upper Outlier: '. $metrics['time_avg_top_outliers'] . ' ms   ';
  			$string = <<<EOD
  <table cellspacing="0" cellpadding="2" border="0" width="100%" style="table-layout:auto">
  	<tr>
 -		<th style="background-color: black; color:white;" colspan="5">
 +		<th style="background-color: black; color:white;" colspan="7">
  			Application Log
  		</th>
 -	</tr><tr style="background-color: #ccc; color:black">
 +	</tr>
 +	<tr>
 +		<th style="background-color: black; color:white;" colspan="7">
 +			Memory Stats-        
 +				Top Value: {$metrics['mem_top']}   
 +				{$top_outliers}  
 +				Q1: {$metrics['mem_q1']}   
 +				Median: {$metrics['mem_median']}   
 +				Q3: {$metrics['mem_q3']}    
 +				Bottom Value: {$metrics['mem_bottom']}   
 +		</th>
 +	</tr>
 +	<tr>
 +		<th style="background-color: black; color:white;" colspan="7">
 +			Time Stats-        
 +				Top Value: {$metrics['time_top']} ms   
 +				{$time_top_outliers}  
 +				Q1: {$metrics['time_q1']} ms   
 +				Median: {$metrics['time_median']} ms   
 +				Q3: {$metrics['time_q3']} ms    
 +				Bottom Value: {$metrics['time_bottom']} ms   
 +		</th>
 +	</tr>
 +	<tr style="background-color: #ccc; color:black">
  	    <th style="width: 15px"> </th>
 -		<th style="width: auto">Category</th><th style="width: auto">Message</th><th style="width: 120px">Time Spent (s)</th><th style="width: 150px">Cumulated Time Spent (s)</th>
 +		<th style="width: auto">Category</th>
 +		<th style="width: auto">Message</th>
 +		<th style="width: 120px">Time Spent (s)</th>
 +		<th style="width: 150px">Cumulated Time Spent (s)</th>
 +		<th style="width: 100px">Δ Memory</th>
 +		<th style="width: 120px">Memory</th>
  	</tr>
  EOD;
  		}
  		return $string;
  	}
 -	protected function renderMessage($log, $info)
 +	protected function renderMessage($log)
  	{
  		$string = '';
 -		$total = sprintf('%0.6f', $info['total']);
 -		$delta = sprintf('%0.6f', $info['delta']);
 +		$total = sprintf('%0.6f', $log['time_total']);
 +		$delta = sprintf('%0.6f', $log['time_delta']);
 +		$mem_total = $log['mem_total'];
 +		$mem_delta = $log['mem_delta'];
  		$msg = preg_replace('/\(line[^\)]+\)$/','',$log[0]); //remove line number info
  		$msg = THttpUtility::htmlEncode($msg);
  		if($this->getCssClass())
  		{
 +			//$log[1] = 0xF;
 +			
  			$colorCssClass = $log[1];
 -			$messageCssClass = $info['even'] ? 'even' : 'odd';
 +			$memcolor = $log['top_outlier'] ? 'high-memory': ($mem_delta < 0 ? 'negative-memory': '');
 +			$timecolor = $log['time_top_outlier'] ? 'high-time': ($delta > 0.001 ? 'medium-time': '');
  			$string = <<<EOD
 -	<tr class="message level$colorCssClass $messageCssClass">
 -		<td class="code"> </td>
 +	<tr class="message">
 +		<td class="code level-$colorCssClass"> </td>
  		<td class="category">{$log[2]}</td>
  		<td class="message">{$msg}</td>
 -		<td class="time">{$delta}</td>
 +		<td class="time $timecolor">{$delta}</td>
  		<td class="cumulatedtime">{$total}</td>
 +		<td class="mem_change $memcolor">{$mem_delta}</td>
 +		<td class="mem_total">{$mem_total}</td>
  	</tr>
  EOD;
  		}
  		else
  		{
 -			$bgcolor = $info['even'] ? "#fff" : "#eee";
 +			$bgcolor = $log['even'] ? "#fff" : "#eee";
  			$color = $this->getColorLevel($log[1]);
 +			$memcolor = $log['top_outlier'] ? '#e00': ($mem_delta < 0 ? '#080': '');
 +			$timecolor = $log['time_top_outlier'] ? '#e00': ($delta > 0.001 ? '#00c': '');
  			$string = <<<EOD
  	<tr style="background-color: {$bgcolor}; color:#000">
  		<td style="border:1px solid silver;background-color: $color;"> </td>
  		<td>{$log[2]}</td>
  		<td>{$msg}</td>
 -		<td style="text-align:center">{$delta}</td>
 +		<td style="text-align:center; color: $timecolor">{$delta}</td>
  		<td style="text-align:center">{$total}</td>
 +		<td style="text-align:center; color: $memcolor">{$mem_delta}</td>
 +		<td style="text-align:center">{$mem_total}</td>
  	</tr>
  EOD;
  		}
 @@ -773,15 +1489,15 @@ EOD;  		$string = '';
  		if($this->getCssClass())
  		{
 -			$string .= '<tr class="footer"><td colspan="5">';
 +			$string .= '<tr class="footer"><td colspan="7" align="center">';
  			foreach(self::$_levelValues as $name => $level)
  			{
 -				$string .= '<span class="level'.$level.'">'.strtoupper($name)."</span>";
 +				$string .= '<span class="level-'.$level.'">'.strtoupper($name)."</span>";
  			}
  		}
  		else
  		{
 -			$string .= "<tr><td colspan=\"5\" style=\"text-align:center; background-color:black; border-top: 1px solid #ccc; padding:0.2em;\">";
 +			$string .= "<tr><td colspan=\"7\" style=\"text-align:center; background-color:black; border-top: 1px solid #ccc; padding:0.2em;\">";
  			foreach(self::$_levelValues as $name => $level)
  			{
  				$string .= "<span style=\"color:white; border:1px solid white; background-color:".$this->getColorLevel($level);
 @@ -794,6 +1510,89 @@ EOD;  }
 +
 +
 +/**
 + * TArraySorter class.
 + * TArraySorter allows one to easily sort an array based on the value of a specific key
 + *
 + * @author Brad Anderson <javalizard@gmail.com>
 + * @version $Id$
 + * @package System
 + * @since 3.2a
 + */
 +class TArraySorter {
 +	private $_v;
 +	public function __construct($v) {
 +		$this->_v = $v;
 +	}
 +	public function sort_func($a, $b) {
 +		return $a[$this->_v] > $b[$this->_v];
 +	}
 +	public function sort_func_rev($a, $b) {
 +		return $a[$this->_v] < $b[$this->_v];
 +	}
 +	public function avsort(&$array) {
 +		uasort($array, array($this, 'sort_func'));
 +	}
 +	public function vsort(&$array) {
 +		usort($array, array($this, 'sort_func'));
 +	}
 +	public function avrsort(&$array) {
 +		uasort($array, array($this, 'sort_func_rev'));
 +	}
 +	public function vrsort(&$array) {
 +		usort($array, array($this, 'sort_func_rev'));
 +	}
 +}
 +
 +
 +/**
 + * This sorts an array of arrays based on a the value of a key in the child array 
 + * This sort drops all associations and reindexes the keys numerically in order
 + * @param array &$array of arrays to be sorted
 + * @param string $key the $key in the child arrays to use to sort by
 + */
 +function vsort(&$array, $key) {
 +	$vsort = new ArraySorter($key);
 +	$vsort->vsort($array);
 +	unset($vsort);
 +}
 +/**
 + * This sorts an array of arrays based on a the value of a key in the child array
 + * This sort keeps all associations but reorders the array
 + * @param array &$array of arrays to be sorted
 + * @param string $key the $key in the child arrays to use to sort by
 + */
 +function avsort(&$array, $key) {
 +	$uvsort = new ArraySorter($key);
 +	$uvsort->avsort($array);
 +	unset($uvsort);
 +}
 +/**
 + * This sorts an array of arrays based on a the value of a key in the child array in reverse order
 + * This sort drops all associations and reindexes the keys numerically in order
 + * @param array &$array of arrays to be sorted
 + * @param string $key the $key in the child arrays to use to sort by
 + */
 +function vrsort(&$array, $key) {
 +	$vsort = new ArraySorter($key);
 +	$vsort->vrsort($array);
 +	unset($vsort);
 +}
 +/**
 + * This sorts an array of arrays based on a the value of a key in the child array in reverse order
 + * This sort keeps all associations but reorders the array
 + * @param array &$array of arrays to be sorted
 + * @param string $key the $key in the child arrays to use to sort by
 + */
 +function avrsort(&$array, $key) {
 +	$vsort = new ArraySorter($key);
 +	$vsort->avrsort($array);
 +	unset($vsort);
 +}
 +
 +
  /**
   * TDbLogRoute class
   *
 @@ -858,16 +1657,16 @@ class TDbLogRoute extends TLogRoute  	 */
  	public function init($config)
  	{
 -		$db=$this->getDbConnection();
 -		$db->setActive(true);
 -
 -		$sql='SELECT * FROM '.$this->_logTable.' WHERE 0';
 -		try
 -		{
 -			$db->createCommand($sql)->query()->close();
 +		if(is_array($config)) {
 +			if(isset($config['connectionid']))
 +				$this->ConnectionID = $config['connectionid'];
 +			if(isset($config['logtablename']))
 +				$this->LogTableName = $config['logtablename'];
 +			if(isset($config['autocreatelogtable']))
 +				$this->AutoCreateLogTable = $config['autocreatelogtable'];
  		}
 -		catch(Exception $e)
 -		{
 +		
 +		if(!$this->checkForTable()) {
  			// DB table not exists
  			if($this->_autoCreate)
  				$this->createDbTable();
 @@ -877,6 +1676,57 @@ class TDbLogRoute extends TLogRoute  		parent::init($config);
  	}
 +	
 +	/**
 +	 *	This checks for the existance of the log table
 +	 * @return boolean true if the table exists, false if not
 +	 */
 +	protected function checkForTable() {
 +		
 +		$db=$this->getDbConnection();
 +		$db->setActive(true);
 +
 +		$sql='SELECT * FROM '.$this->_logTable.' WHERE 0';
 +		try
 +		{
 +			$db->createCommand($sql)->query()->close();
 +			return true;
 +		} catch( Exception $e ) {
 +			return false;
 +		}
 +	}
 +	
 +	
 +	/**
 +	 *	@return string this encodes the TDbLogRoute as xml
 +	 */
 +	public function toXml() {
 +		$xml = '<route ' .$this->encodeId(). $this->encodeName().$this->encodeClass() . $this->encodeLevels() . 
 +			$this->encodeCategories() . $this->encodeControls() . $this->encodeRoles() . $this->encodeConnectionID(). 
 +			$this->encodeLogTableName(). $this->encodeAutoCreateLogTable().'/>';
 +		return $xml;
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the id of the database module of the route as an xml attribute
 +	 */
 +	protected function encodeConnectionID() {
 +		return 'connectionid="'. addslashes($this->ConnectionID) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the table name of the route as an xml attribute
 +	 */
 +	protected function encodeLogTableName() {
 +		return 'logtablename="'. addslashes($this->LogTableName) .'" ';
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the auto create log table of the route as an xml attribute
 +	 */
 +	protected function encodeAutoCreateLogTable() {
 +		return 'autocreatelogtable="'. $this->AutoCreateLogTable .'" ';
 +	}
  	/**
  	 * Stores log messages into database.
 @@ -884,15 +1734,27 @@ class TDbLogRoute extends TLogRoute  	 */
  	protected function processLogs($logs)
  	{
 -		$sql='INSERT INTO '.$this->_logTable.'(level, category, logtime, message) VALUES (:level, :category, :logtime, :message)';
 -		$command=$this->getDbConnection()->createCommand($sql);
 -		foreach($logs as $log)
 -		{
 -			$command->bindValue(':message',$log[0]);
 -			$command->bindValue(':level',$log[1]);
 -			$command->bindValue(':category',$log[2]);
 -			$command->bindValue(':logtime',$log[3]);
 -			$command->execute();
 +		try {
 +			$sql='INSERT INTO '.$this->_logTable.'(metakey, userid, level, category, memory, logtime, message) VALUES (:metakey, :userid, :level, :category, :memory, :logtime, :message)';
 +			$command=$this->getDbConnection()->createCommand($sql);
 +			foreach($logs as $log)
 +			{
 +				$command->bindValue(':metakey',$this->MetaId);
 +				$command->bindValue(':userid',$this->UserId);
 +				$command->bindValue(':level',$log[1]);
 +				$command->bindValue(':category',$log[2]);
 +				$command->bindValue(':memory',$log[4]);
 +				$command->bindValue(':logtime',$log[3]);
 +				$command->bindValue(':message',$log[0]);
 +				$command->execute();
 +			}
 +		} catch(Exception $e) {
 +			// table may be deleted from when this was instantiated
 +			//probable case: deleted table (aka. dumped database), and don't fail in this case
 +			
 +			//If the table is there, something else is up and rethrow error
 +			if($this->checkForTable())
 +				throw $e;
  		}
  	}
 @@ -907,13 +1769,17 @@ class TDbLogRoute extends TLogRoute  		$autoidAttributes = '';
  		if($driver==='mysql')
  			$autoidAttributes = 'AUTO_INCREMENT';
 -
 +		
 +		// metakey = varchar 39 because that's the size of an IPv6 address
  		$sql='CREATE TABLE '.$this->_logTable.' (
  			log_id INTEGER NOT NULL PRIMARY KEY ' . $autoidAttributes . ',
 -			level INTEGER,
 +			metakey VARCHAR(39),
 +			userid BIGINT,
 +			level INTEGER NOT NULL,
  			category VARCHAR(128),
 -			logtime VARCHAR(20),
 -			message VARCHAR(255))';
 +			memory INTEGER NOT NULL,
 +			logtime DECIMAL(20,8) NOT NULL,
 +			message VARCHAR(255), INDEX(metakey), INDEX(userid), INDEX(level), INDEX(category), INDEX(logtime))';
  		$db->createCommand($sql)->execute();
  	}
 @@ -1028,6 +1894,7 @@ class TDbLogRoute extends TLogRoute   */
  class TFirebugLogRoute extends TBrowserLogRoute
  {
 +	
  	protected function renderHeader ()
  	{
  		$string = <<<EOD
 @@ -1036,19 +1903,19 @@ class TFirebugLogRoute extends TBrowserLogRoute  /*<![CDATA[*/
  if (typeof(console) == 'object')
  {
 -	console.log ("[Cumulated Time] [Time] [Level] [Category] [Message]");
 +	console.log ("[Cumulated Time] [Time] [Memory] [Level] [Category] [Message]");
  EOD;
  		return $string;
  	}
 -	protected function renderMessage ($log, $info)
 +	protected function renderMessage ($log)
  	{
  		$logfunc = $this->getFirebugLoggingFunction($log[1]);
 -		$total = sprintf('%0.6f', $info['total']);
 -		$delta = sprintf('%0.6f', $info['delta']);
 -		$msg = trim($this->formatLogMessage($log[0],$log[1],$log[2],''));
 +		$total = sprintf('%0.6f', $log['time_total']);
 +		$delta = sprintf('%0.6f', $log['time_delta']);
 +		$msg = trim($this->formatLogMessage($log[0],$log[1],$log[2],'',$log[4]));
  		$msg = preg_replace('/\(line[^\)]+\)$/','',$msg); //remove line number info
  		$msg = "[{$total}] [{$delta}] ".$msg; // Add time spent and cumulated time spent
  		$string = $logfunc . '(\'' . addslashes($msg) . '\');' . "\n";
 @@ -1102,7 +1969,7 @@ EOD;   * @package System.Util
   * @since 3.1.5
   */
 -class TFirePhpLogRoute extends TLogRoute
 +class TFirePhpLogRoute extends TLogRoute implements IHeaderRoute
  {
  	/**
  	 * Default group label
 @@ -1111,6 +1978,72 @@ class TFirePhpLogRoute extends TLogRoute  	private $_groupLabel = null;
 +	static public function available() {
 +		require_once Prado::getPathOfNamespace('System.3rdParty.FirePHPCore') . '/FirePHP.class.php';
 +		$instance = FirePHP::getInstance(true);
 +		$available = $instance->detectClientExtension();
 +		return $available;
 +	}
 +
 +	/**
 +	 * Initializes the route.
 +	 * @param TXmlElement configurations specified in {@link TLogRouter}.
 +	 */
 +	public function init($config)
 +	{
 +		parent::init($config);
 +		
 +		if(is_array($config)) {
 +			if(isset($config['grouplabel']))
 +				$this->GroupLabel = $config['grouplabel'];
 +		}
 +		if($this->Application->Service instanceof IPageEvents) {
 +			$this->Application->Service->OnPreRenderComplete[] = array($this, 'checkHeadFlush');
 +		}
 +	}
 +	
 +	/**
 +	 * Not having the head tag flush when it's done is a small price to pay to enable firephp
 +	 * @param TXmlElement configurations specified in {@link TLogRouter}.
 +	 */
 +	public function checkHeadFlush($sender, $page) {
 +		if(!$this->Active) return;
 +		$heads = $page->findControlsByType('THead');
 +		
 +		// there should only be one but it's an array, so why not?
 +		foreach($heads as $head) {
 +			if($head->RenderFlush) {
 +				Prado::log('Turning off head flush option for firephp', TLogger::INFO, 'System.Util.TFirePhpLogRoute');
 +				$head->RenderFlush = false;
 +			}
 +		}
 +	}
 +	
 +	
 +	
 +	
 +	/**
 +	 *	@return string this encodes the TFirePhpLogRoute of the route as an xml attribute
 +	 */
 +	public function toXml() {
 +		$xml = '<route ' .$this->encodeId(). $this->encodeName().$this->encodeClass() . $this->encodeLevels() . 
 +			$this->encodeCategories() . $this->encodeControls() . $this->encodeRoles() . $this->encodeGroupLabel(). '/>';
 +		return $xml;
 +	}
 +	
 +	/**
 +	 *	@return string this encodes the grouplabel of the route as an xml attribute
 +	 */
 +	protected function encodeGroupLabel() {
 +		if($this->GroupLabel == self::DEFAULT_LABEL) return '';
 +		return 'grouplabel="'. addslashes($this->GroupLabel) .'" ';
 +	}
 +	
 +
 +	/**
 +	 * Stores log messages into database.
 +	 * @param array list of log messages
 +	 */
  	public function processLogs($logs)
  	{
  		if(empty($logs) || $this->getApplication()->getMode()==='Performance') return;
 @@ -1122,7 +2055,7 @@ class TFirePhpLogRoute extends TLogRoute  					Routing to FirePHP impossible, because headers already sent!
  				</div>
  			';
 -			$fallback = new TBrowserLogRoute();
 +			$fallback = new TFirebugLogRoute();
  			$fallback->processLogs($logs);
  			return;
  		}
 @@ -1195,6 +2128,6 @@ class TFirePhpLogRoute extends TLogRoute  	 */
  	public function setGroupLabel($value)
  	{
 -		$this->_groupLabel=$value;
 +		$this->_groupLabel=$value ? $value : null;
  	}
  }
\ No newline at end of file diff --git a/framework/Util/TLogger.php b/framework/Util/TLogger.php index 51005883..388a5241 100644 --- a/framework/Util/TLogger.php +++ b/framework/Util/TLogger.php @@ -4,7 +4,7 @@   *
   * @author Qiang Xue <qiang.xue@gmail.com>
   * @link http://www.pradosoft.com/
 - * @copyright Copyright © 2005-2008 PradoSoft + * @copyright Copyright © 2005-2010 PradoSoft
   * @license http://www.pradosoft.com/license/
   * @version $Id$
   * @package System.Util
 @@ -14,8 +14,8 @@   * TLogger class.
   *
   * TLogger records log messages in memory and implements the methods to
 - * retrieve the messages with filter conditions, including log levels and
 - * log categories.
 + * retrieve the messages with filter conditions, including log levels,
 + * log categories, and by control.
   *
   * @author Qiang Xue <qiang.xue@gmail.com>
   * @version $Id$
 @@ -46,6 +46,10 @@ class TLogger extends TComponent  	 * @var array list of categories to be filtered
  	 */
  	private $_categories;
 +	/**
 +	 * @var array list of control client ids to be filtered
 +	 */
 +	private $_controls;
  	/**
  	 * Logs a message.
 @@ -55,59 +59,77 @@ class TLogger extends TComponent  	 * TLogger::DEBUG, TLogger::INFO, TLogger::NOTICE, TLogger::WARNING,
  	 * TLogger::ERROR, TLogger::ALERT, TLogger::FATAL.
  	 * @param string category of the message
 +	 * @param string|TControl control of the message
  	 */
 -	public function log($message,$level,$category='Uncategorized')
 +	public function log($message,$level,$category='Uncategorized', $ctl=null)
  	{
 -		$this->_logs[]=array($message,$level,$category,microtime(true));
 +		if($ctl) {
 +			if($ctl instanceof TControl)
 +				$ctl = $ctl->ClientId;
 +			else if(!is_string($ctl))
 +				$ctl = null;
 +		} else
 +			$ctl = null;
 +		$this->_logs[]=array($message,$level,$category,microtime(true),memory_get_usage(),$ctl);
  	}
  	/**
  	 * Retrieves log messages.
 -	 * Messages may be filtered by log levels and/or categories.
 +	 * Messages may be filtered by log levels and/or categories and/or control client ids.
  	 * A level filter is specified by an integer, whose bits indicate the levels interested.
  	 * For example, (TLogger::INFO | TLogger::WARNING) specifies INFO and WARNING levels.
 -	 * A category filter is specified by concatenating interested category names
 -	 * with commas. A message whose category name starts with any filtering category
 -	 * will be returned. For example, a category filter 'System.Web, System.IO'
 +	 * A category filter is specified by an array of categories to filter. 
 +	 * A message whose category name starts with any filtering category
 +	 * will be returned. For example, a category filter array('System.Web','System.IO')
  	 * will return messages under categories such as 'System.Web', 'System.IO',
  	 * 'System.Web.UI', 'System.Web.UI.WebControls', etc.
 -	 * Level filter and category filter are combinational, i.e., only messages
 -	 * satisfying both filter conditions will they be returned.
 +	 * A control client id filter is specified by an array of control client id
 +	 * A message whose control client id starts with any filtering naming panels
 +	 * will be returned. For example, a category filter array('ctl0_body_header', 
 +	 * 'ctl0_body_content_sidebar')
 +	 * will return messages under categories such as 'ctl0_body_header', 'ctl0_body_content_sidebar',
 +	 * 'ctl0_body_header_title', 'ctl0_body_content_sidebar_savebutton', etc.
 +	 * Level filter, category filter, and control filter are combinational, i.e., only messages
 +	 * satisfying all filter conditions will they be returned.
  	 * @param integer level filter
 -	 * @param string category filter
 -	 * @param array list of messages. Each array elements represents one message
 +	 * @param array category filter
 +	 * @param array control filter
 +	 * @return array list of messages. Each array elements represents one message
  	 * with the following structure:
  	 * array(
  	 *   [0] => message
  	 *   [1] => level
  	 *   [2] => category
  	 *   [3] => timestamp (by microtime(), float number));
 +	 *   [4] => memory in bytes
 +	 *   [5] => control client id
  	 */
 -	public function getLogs($levels=null,$categories=null)
 +	public function getLogs($levels=null,$categories=null,$controls=null)
  	{
  		$this->_levels=$levels;
  		$this->_categories=$categories;
 -		if(empty($levels) && empty($categories))
 +		$this->_controls=$controls;
 +		if(empty($levels) && empty($categories) && empty($controls))
  			return $this->_logs;
 -		else if(empty($levels))
 -			return array_values(array_filter(array_filter($this->_logs,array($this,'filterByCategories'))));
 -		else if(empty($categories))
 -			return array_values(array_filter(array_filter($this->_logs,array($this,'filterByLevels'))));
 -		else
 -		{
 -			$ret=array_values(array_filter(array_filter($this->_logs,array($this,'filterByLevels'))));
 -			return array_values(array_filter(array_filter($ret,array($this,'filterByCategories'))));
 -		}
 +		$logs = $this->_logs;
 +		if(!empty($levels))
 +			$logs = array_values(array_filter( array_filter($logs,array($this,'filterByLevels')) ));
 +		if(!empty($categories))
 +			$logs = array_values(array_filter( array_filter($logs,array($this,'filterByCategories')) ));
 +		if(!empty($controls))
 +			$logs = array_values(array_filter( array_filter($logs,array($this,'filterByControl')) ));
 +		return $logs;
  	}
  	/**
 -	 * Filter function used by {@link getLogs}
 +	 * Filter function used by {@link getLogs}.
  	 * @param array element to be filtered
  	 */
  	private function filterByCategories($value)
  	{
  		foreach($this->_categories as $category)
  		{
 +			// element 2 is the category
  			if($value[2]===$category || strpos($value[2],$category.'.')===0)
  				return $value;
  		}
 @@ -120,10 +142,26 @@ class TLogger extends TComponent  	 */
  	private function filterByLevels($value)
  	{
 +		// element 1 are the levels
  		if($value[1] & $this->_levels)
  			return $value;
  		else
  			return false;
  	}
 +
 +	/**
 +	 * Filter function used by {@link getLogs}
 +	 * @param array element to be filtered
 +	 */
 +	private function filterByControl($ctl)
 +	{
 +		// element 5 are the control client ids
 +		foreach($this->_controls as $control)
 +		{
 +			if($value[5]===$control || strpos($value[5],$control)===0)
 +				return $value;
 +		}
 +		return false;
 +	}
  }
  | 
