diff options
| -rw-r--r-- | framework/Exceptions/messages.txt | 12 | ||||
| -rw-r--r-- | framework/Log/TLogRouter.php | 355 | ||||
| -rw-r--r-- | framework/Log/TLogger.php | 73 | ||||
| -rw-r--r-- | framework/core.php | 27 | 
4 files changed, 432 insertions, 35 deletions
| diff --git a/framework/Exceptions/messages.txt b/framework/Exceptions/messages.txt index 54c8f04c..9626e261 100644 --- a/framework/Exceptions/messages.txt +++ b/framework/Exceptions/messages.txt @@ -163,4 +163,14 @@ bulletedlist_selectedindex_unsupported	= TBulletedList.SelectedIndex is read-onl  bulletedlist_selectedindices_unsupported= TBulletedList.SelectedIndices is read-only.
  bulletedlist_selectedvalue_unsupported	= TBulletedList.SelectedValue is read-only.
 -radiobuttonlist_selectedindices_unsupported	= TRadioButtonList.SelectedIndices is read-only.
\ No newline at end of file +radiobuttonlist_selectedindices_unsupported	= TRadioButtonList.SelectedIndices is read-only.
 +
 +logrouter_configfile_invalid			= TLogRouter.ConfigFile '%s' does not exist.
 +logrouter_routeclass_required			= Class attribute is required in <route> configuration.
 +logrouter_routetype_required			= Log route must be an instance of TLogRoute or its derived class.
 +
 +filelogroute_logpath_invalid			= TFileLogRoute.LogPath '%s' must be a directory in namespace format and must be writable by the Web server process.
 +filelogroute_maxfilesize_invalid		= TFileLogRoute.MaxFileSize must be greater than 0.
 +filelogroute_maxlogfiles_invalid		= TFileLogRoute.MaxLogFiles must be greater than 0.
 +
 +emaillogroute_sentfrom_required			= TEmailLogRoute.SentFrom cannot be empty.
\ No newline at end of file diff --git a/framework/Log/TLogRouter.php b/framework/Log/TLogRouter.php index 98bded1c..7a09c758 100644 --- a/framework/Log/TLogRouter.php +++ b/framework/Log/TLogRouter.php @@ -1,11 +1,59 @@  <?php
 +/**
 + * TLogRouter, TLogRoute, TFileLogRoute, TEmailLogRoute class file
 + *
 + * @author Qiang Xue <qiang.xue@gmail.com>
 + * @link http://www.pradosoft.com/
 + * @copyright Copyright © 2005 PradoSoft
 + * @license http://www.pradosoft.com/license/
 + * @version $Revision: $  $Date: $
 + * @package System.Log
 + */
 +/**
 + * TLogRouter class.
 + *
 + * TLogRouter manages routes that record log messages in different media different ways.
 + * For example, a file log route {@link TFileLogRoute} records log messages
 + * in log files. An email log route {@link TEmailLogRoute} sends log messages
 + * to email addresses.
 + *
 + * Log routes may be configured in application or page folder configuration files
 + * 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="TEmailLogRoute" Categories="Application" Levels="Fatal" Emails="admin@pradosoft.com" />
 + * </code>
 + * You can specify multiple routes with different filtering conditions and different
 + * targets, even if the routes are of the same type.
 + *
 + * @author Qiang Xue <qiang.xue@gmail.com>
 + * @version $Revision: $  $Date: $
 + * @package System.Log
 + * @since 3.0
 + */
  class TLogRouter extends TModule
  {
 +	/**
 +	 * File extension of external configuration file
 +	 */
  	const CONFIG_FILE_EXT='.xml';
 +	/**
 +	 * @var array list of routes available
 +	 */
  	private $_routes=array();
 +	/**
 +	 * @var string external configuration file
 +	 */
  	private $_configFile=null;
 +	/**
 +	 * Initializes this module.
 +	 * This method is required by the IModule interface.
 +	 * @param TXmlElement configuration for this module, can be null
 +	 * @throws TConfigurationException if {@link getConfigFile ConfigFile} is invalid.
 +	 */
  	public function init($config)
  	{
  		if($this->_configFile!==null)
 @@ -23,6 +71,11 @@ class TLogRouter extends TModule  		$this->getApplication()->attachEventHandler('EndRequest',array($this,'collectLogs'));
  	}
 +	/**
 +	 * Loads configuration from an XML element
 +	 * @param TXmlElement configuration node
 +	 * @throws TConfigurationException if log route class or type is not specified
 +	 */
  	private function loadConfig($xml)
  	{
  		foreach($xml->getElementsByTagName('route') as $routeConfig)
 @@ -32,7 +85,7 @@ class TLogRouter extends TModule  				throw new TConfigurationException('logrouter_routeclass_required');
  			$route=Prado::createComponent($class);
  			if(!($route instanceof TLogRoute))
 -				throw new TConfigurationException('logrouter_routetype_required');
 +				throw new TConfigurationException('logrouter_routetype_invalid');
  			foreach($properties as $name=>$value)
  				$route->setSubproperty($name,$value);
  			$this->_routes[]=$route;
 @@ -40,27 +93,65 @@ class TLogRouter extends TModule  		}
  	}
 +	/**
 +	 * @return string external configuration file. Defaults to null.
 +	 */
  	public function getConfigFile()
  	{
  		return $this->_configFile;
  	}
 +	/**
 +	 * @param string external configuration file in namespace format. The file
 +	 * must be suffixed with '.xml'.
 +	 * @throws TInvalidDataValueException if the file is invalid.
 +	 */
  	public function setConfigFile($value)
  	{
  		if(($this->_configFile=Prado::getPathOfNamespace($value,self::LOG_FILE_EXT))===null)
  			throw new TConfigurationException('logrouter_configfile_invalid',$value);
  	}
 +	/**
 +	 * Collects log messages from a logger.
 +	 * This method is an event handler to application's EndRequest event.
 +	 * @param mixed event parameter
 +	 */
  	public function collectLogs($param)
  	{
 +		$logger=Prado::getLogger();
  		foreach($this->_routes as $route)
 -			$route->collectLogs();
 +			$route->collectLogs($logger);
  	}
  }
 -
 +/**
 + * TLogRoute class.
 + *
 + * TLogRoute is the base class for all log route classes.
 + * A log route object retrieves log messages from a logger and sends it
 + * somewhere, such as files, emails.
 + * The messages being retrieved may be filtered first before being sent
 + * to the destination. The filters include log level filter and log category filter.
 + *
 + * To specify level filter, set {@link setLevels Levels} property,
 + * which takes a string of comma-separated desired level names (e.g. 'Error, Debug').
 + * To specify category filter, set {@link setCategories Categories} property,
 + * which takes a string of comma-separated desired category names (e.g. 'System.Web, System.IO').
 + *
 + * Level filter and category filter are combinational, i.e., only messages
 + * satisfying both filter conditions will they be returned.
 + *
 + * @author Qiang Xue <qiang.xue@gmail.com>
 + * @version $Revision: $  $Date: $
 + * @package System.Log
 + * @since 3.0
 + */
  abstract class TLogRoute extends TComponent
  {
 +	/**
 +	 * @var array lookup table for level names
 +	 */
  	private static $_levelNames=array(
  		TLogger::ERROR=>'Error',
  		TLogger::DEBUG=>'Debug',
 @@ -71,6 +162,9 @@ abstract class TLogRoute extends TComponent  		TLogger::ALERT=>'Alert',
  		TLogger::FATAL=>'Fatal'
  	);
 +	/**
 +	 * @var array lookup table for level values
 +	 */
  	private static $_levelValues=array(
  		'error'=>TLogger::ERROR,
  		'debug'=>TLogger::DEBUG,
 @@ -81,115 +175,231 @@ abstract class TLogRoute extends TComponent  		'alert'=>TLogger::ALERT,
  		'fatal'=>TLogger::FATAL
  	);
 +	/**
 +	 * @var integer log level filter (bits)
 +	 */
  	private $_levels=null;
 +	/**
 +	 * @var array log category filter
 +	 */
  	private $_categories=null;
 +	/**
 +	 * Initializes the route.
 +	 * @param TXmlElement configurations specified in {@link TLogRouter}.
 +	 */
  	public function init($config)
  	{
  	}
 +	/**
 +	 * @return integer log level filter
 +	 */
  	public function getLevels()
  	{
  		return $this->_levels;
  	}
 +	/**
 +	 * @param integer|string integer log level filter (in bits). If the value is
 +	 * a string, it is assumed to be comma-separated level names. Valid level names
 +	 * include 'Error', 'Debug', 'Info', 'Notice', 'Warning', 'Error', 'Alert' and 'Fatal'.
 +	 */
  	public function setLevels($levels)
  	{
 -		$this->_levels=null;
 -		$levels=strtolower($levels);
 -		foreach(explode(',',$levels) as $level)
 +		if(is_integer($levels))
 +			$this->_levels=$levels;
 +		else
  		{
 -			$level=trim($level);
 -			if(isset(self::$_levelValues[$level]))
 -				$this->_levels|=self::$_levelValues[$level];
 +			$this->_levels=null;
 +			$levels=strtolower($levels);
 +			foreach(explode(',',$levels) as $level)
 +			{
 +				$level=trim($level);
 +				if(isset(self::$_levelValues[$level]))
 +					$this->_levels|=self::$_levelValues[$level];
 +			}
  		}
  	}
 +	/**
 +	 * @return array list of categories to be looked for
 +	 */
  	public function getCategories()
  	{
  		return $this->_categories;
  	}
 +	/**
 +	 * @param array|string list of categories to be looked for. If the value is a string,
 +	 * it is assumed to be comma-separated category names.
 +	 */
  	public function setCategories($categories)
  	{
 -		$this->_categories=null;
 -		foreach(explode(',',$categories) as $category)
 +		if(is_array($categories))
 +			$this->_categories=$categories;
 +		else
  		{
 -			if(($category=trim($category))!=='')
 -				$this->_categories[]=$category;
 +			$this->_categories=null;
 +			foreach(explode(',',$categories) as $category)
 +			{
 +				if(($category=trim($category))!=='')
 +					$this->_categories[]=$category;
 +			}
  		}
  	}
 +	/**
 +	 * @param integer level value
 +	 * @return string level name
 +	 */
  	protected function getLevelName($level)
  	{
  		return isset(self::$_levelNames[$level])?self::$_levelNames[$level]:'Unknown';
  	}
 +	/**
 +	 * @param string level name
 +	 * @return integer level value
 +	 */
 +	protected function getLevelValue($level)
 +	{
 +		return isset(self::$_levelValues[$level])?self::$_levelValues[$level]:0;
 +	}
 +
 +	/**
 +	 * Formats a log message given different fields.
 +	 * @param string message content
 +	 * @param integer message level
 +	 * @param string message category
 +	 * @param integer timestamp
 +	 * @return string formatted message
 +	 */
  	protected function formatLogMessage($message,$level,$category,$time)
  	{
  		return @date('M d H:i:s',$time).' ['.$this->getLevelName($level).'] ['.$category.'] '.$message."\n";
  	}
 -	public function collectLogs()
 +	/**
 +	 * Retrieves log messages from logger to log route specific destination.
 +	 * @param TLogger logger instance
 +	 */
 +	public function collectLogs(TLogger $logger)
  	{
 -		$logs=Prado::getLogger()->getLogs($this->getLevels(),$this->getCategories());
 +		$logs=$logger->getLogs($this->getLevels(),$this->getCategories());
  		$this->processLogs($logs);
  	}
 +	/**
 +	 * Processes log messages and sends them to specific destination.
 +	 * Derived child classes must implement this method.
 +	 * @param array list of messages. Each array element is a (formatted) message string.
 +	 */
  	abstract protected function processLogs($logs);
  }
 +/**
 + * TFileLogRoute class.
 + *
 + * TFileLogRoute records log messages in files.
 + * The log files are stored under {@link setLogPath LogPath} and the file name
 + * is specified by {@link setLogFile LogFile}. If the size of the log file is
 + * greater than {@link setMaxFileSize MaxFileSize} (in kilo-bytes), a rotation
 + * is performed, which renames the current log file by suffixing the file name
 + * with '.1'. All existing log files are moved backwards one place, i.e., '.2'
 + * to '.3', '.1' to '.2'. The property {@link setMaxLogFiles MaxLogFiles}
 + * specifies how many files to be kept.
 + *
 + * @author Qiang Xue <qiang.xue@gmail.com>
 + * @version $Revision: $  $Date: $
 + * @package System.Log
 + * @since 3.0
 + */
  class TFileLogRoute extends TLogRoute
  {
 +	/**
 +	 * @var integer maximum log file size
 +	 */
  	private $_maxFileSize=1024; // in KB
 +	/**
 +	 * @var integer number of log files used for rotation
 +	 */
  	private $_maxLogFiles=2;
 +	/**
 +	 * @var string directory storing log files
 +	 */
  	private $_logPath=null;
 +	/**
 +	 * @var string log file name
 +	 */
  	private $_logFile='prado.log';
 -	public function init($config)
 +	/**
 +	 * @return string directory storing log files. Defaults to application runtime path.
 +	 */
 +	public function getLogPath()
  	{
  		if($this->_logPath===null)
  			$this->_logPath=$this->getApplication()->getRuntimePath();
 -	}
 -
 -	public function getLogPath()
 -	{
  		return $this->_logPath;
  	}
 +	/**
 +	 * @param string directory (in namespace format) storing log files.
 +	 * @throws TConfigurationException if log path is invalid
 +	 */
  	public function setLogPath($value)
  	{
 -		if(($this->_logPath=Prado::getPathOfNamespace($value))===null)
 +		if(($this->_logPath=Prado::getPathOfNamespace($value))===null || !is_dir($this->_logPath) || !is_writable($this->_logPath))
  			throw new TConfigurationException('filelogroute_logpath_invalid',$value);
  	}
 +	/**
 +	 * @return string log file name. Defaults to 'prado.log'.
 +	 */
  	public function getLogFile()
  	{
  		return $this->_logFile;
  	}
 +	/**
 +	 * @param string log file name
 +	 */
  	public function setLogFile($value)
  	{
  		$this->_logFile=$value;
  	}
 +	/**
 +	 * @return integer maximum log file size in kilo-bytes (KB). Defaults to 1024 (1MB).
 +	 */
  	public function getMaxFileSize()
  	{
  		return $this->_maxFileSize;
  	}
 +	/**
 +	 * @param integer maximum log file size in kilo-bytes (KB).
 +	 * @throws TInvalidDataValueException if the value is smaller than 1.
 +	 */
  	public function setMaxFileSize($value)
  	{
  		$this->_maxFileSize=TPropertyValue::ensureInteger($value);
 -		if($this->_maxFileSize<0)
 +		if($this->_maxFileSize<=0)
  			throw new TInvalidDataValueException('filelogroute_maxfilesize_invalid');
  	}
 +	/**
 +	 * @return integer number of files used for rotation. Defaults to 2.
 +	 */
  	public function getMaxLogFiles()
  	{
  		return $this->_maxLogFiles;
  	}
 +	/**
 +	 * @param integer number of files used for rotation.
 +	 */
  	public function setMaxLogFiles($value)
  	{
  		$this->_maxLogFiles=TPropertyValue::ensureInteger($value);
 @@ -197,18 +407,25 @@ class TFileLogRoute extends TLogRoute  			throw new TInvalidDataValueException('filelogroute_maxlogfiles_invalid');
  	}
 +	/**
 +	 * Saves log messages in files.
 +	 * @param array list of log messages
 +	 */
  	protected function processLogs($logs)
  	{
 -		$logFile=$this->_logPath.'/'.$this->_logFile;
 +		$logFile=$this->getLogPath().'/'.$this->getLogFile();
  		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);
  	}
 +	/**
 +	 * Rotates log files.
 +	 */
  	protected function rotateFiles()
  	{
 -		$file=$this->_logPath.'/'.$this->_logFile;
 +		$file=$this->getLogPath().'/'.$this->getLogFile();
  		for($i=$this->_maxLogFiles;$i>0;--$i)
  		{
  			$rotateFile=$file.'.'.$i;
 @@ -225,59 +442,129 @@ class TFileLogRoute extends TLogRoute  	}
  }
 +/**
 + * TEmailLogRoute class.
 + *
 + * TEmailLogRoute sends selected log messages to email addresses.
 + * The target email addresses may be specified via {@link setEmails Emails} property.
 + * Optionally, you may set the email {@link setSubject Subject} and the
 + * {@link setSentFrom SentFrom} address.
 + *
 + * @author Qiang Xue <qiang.xue@gmail.com>
 + * @version $Revision: $  $Date: $
 + * @package System.Log
 + * @since 3.0
 + */
  class TEmailLogRoute extends TLogRoute
  {
 +	/**
 +	 * Regex pattern for email address.
 +	 */
  	const EMAIL_PATTERN='/^([0-9a-zA-Z]+[-._+&])*[0-9a-zA-Z]+@([-0-9a-zA-Z]+[.])+[a-zA-Z]{2,6}$/';
 +	/**
 +	 * Default email subject.
 +	 */
  	const DEFAULT_SUBJECT='Prado Application Log';
 +	/**
 +	 * @var array list of destination email addresses.
 +	 */
  	private $_emails=array();
 +	/**
 +	 * @var string email subject
 +	 */
  	private $_subject='';
 +	/**
 +	 * @var string email sent from address
 +	 */
  	private $_from='';
 +	/**
 +	 * 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)
 +	{
 +		if($this->_from==='')
 +			$this->_from=ini_get('sendmail_from');
 +		if($this->_from==='')
 +			throw new TConfigurationException('emaillogroute_sentfrom_required');
 +	}
 +
 +	/**
 +	 * Sends log messages to specified email addresses.
 +	 * @param array list of log messages
 +	 */
  	protected function processLogs($logs)
  	{
  		$message='';
 -		$headers=($this->_from==='') ? '' : "From:{$this->_from}\r\n";
 -		$subject=$this->_subject===''?self::DEFAULT_SUBJECT:$this->_subject;
  		foreach($logs as $log)
  			$message.=$this->formatLogMessage($log[0],$log[1],$log[2],$log[3]);
  		$message=wordwrap($message,70);
  		foreach($this->_emails as $email)
 -			mail($email,$subject,$message,$headers);
 +			mail($email,$this->getSubject(),$message,"From:{$this->_from}\r\n");
  	}
 +	/**
 +	 * @return array list of destination email addresses
 +	 */
  	public function getEmails()
  	{
  		return $this->_emails;
  	}
 +	/**
 +	 * @return array|string list of destination email addresses. If the value is
 +	 * a string, it is assumed to be comma-separated email addresses.
 +	 */
  	public function setEmails($emails)
  	{
 -		$this->_emails=array();
 -		foreach(explode(',',$emails) as $email)
 +		if(is_array($emails))
 +			$this->_emails=$emails;
 +		else
  		{
 -			$email=trim($email);
 -			if(preg_match(self::EMAIL_PATTERN,$email))
 -				$this->_emails[]=$email;
 +			$this->_emails=array();
 +			foreach(explode(',',$emails) as $email)
 +			{
 +				$email=trim($email);
 +				if(preg_match(self::EMAIL_PATTERN,$email))
 +					$this->_emails[]=$email;
 +			}
  		}
  	}
 +	/**
 +	 * @return string email subject. Defaults to TEmailLogRoute::DEFAULT_SUBJECT
 +	 */
  	public function getSubject()
  	{
 +		if($this->_subject===null)
 +			$this->_subject=self::DEFAULT_SUBJECT;
  		return $this->_subject;
  	}
 +	/**
 +	 * @param string email subject.
 +	 */
  	public function setSubject($value)
  	{
  		$this->_subject=$value;
  	}
 -	public function getFrom()
 +	/**
 +	 * @return string send from address of the email
 +	 */
 +	public function getSentFrom()
  	{
  		return $this->_from;
  	}
 -	public function setFrom($value)
 +	/**
 +	 * @param string send from address of the email
 +	 */
 +	public function setSentFrom($value)
  	{
  		$this->_from=$value;
  	}
 diff --git a/framework/Log/TLogger.php b/framework/Log/TLogger.php index f9b9b47a..fbe56409 100644 --- a/framework/Log/TLogger.php +++ b/framework/Log/TLogger.php @@ -1,7 +1,32 @@  <?php
 +/**
 + * TLogger class file
 + *
 + * @author Qiang Xue <qiang.xue@gmail.com>
 + * @link http://www.pradosoft.com/
 + * @copyright Copyright © 2005 PradoSoft
 + * @license http://www.pradosoft.com/license/
 + * @version $Revision: $  $Date: $
 + * @package System.Log
 + */
 +/**
 + * 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.
 + *
 + * @author Qiang Xue <qiang.xue@gmail.com>
 + * @version $Revision: $  $Date: $
 + * @package System.Log
 + * @since 3.0
 + */
  class TLogger extends TComponent
  {
 +	/**
 +	 * Log levels.
 +	 */
  	const DEBUG=0x01;
  	const INFO=0x02;
  	const NOTICE=0x04;
 @@ -9,15 +34,55 @@ class TLogger extends TComponent  	const ERROR=0x10;
  	const ALERT=0x20;
  	const FATAL=0x40;
 +	/**
 +	 * @var array log messages
 +	 */
  	private $_logs=array();
 +	/**
 +	 * @var integer log levels (bits) to be filtered
 +	 */
  	private $_levels;
 +	/**
 +	 * @var array list of categories to be filtered
 +	 */
  	private $_categories;
 +	/**
 +	 * Logs a message.
 +	 * Messages logged by this method may be retrieved via {@link getLogs}.
 +	 * @param string message to be logged
 +	 * @param integer level of the message. Valid values include
 +	 * TLogger::DEBUG, TLogger::INFO, TLogger::NOTICE, TLogger::WARNING,
 +	 * TLogger::ERROR, TLogger::ALERT, TLogger::FATAL.
 +	 * @param string category of the message
 +	 */
  	public function log($message,$level,$category='Uncategorized')
  	{
  		$this->_logs[]=array($message,$level,$category,time());
  	}
 +	/**
 +	 * Retrieves log messages.
 +	 * Messages may be filtered by log levels and/or categories.
 +	 * 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'
 +	 * 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.
 +	 * @param integer level filter
 +	 * @param string category filter
 +	 * @param array list of messages. Each array elements represents one message
 +	 * with the following structure:
 +	 * array(
 +	 *   [0] => message
 +	 *   [1] => level
 +	 *   [2] => category
 +	 *   [3] => timestamp);
 +	 */
  	public function getLogs($levels=null,$categories=null)
  	{
  		$this->_levels=$levels;
 @@ -35,6 +100,10 @@ class TLogger extends TComponent  		}
  	}
 +	/**
 +	 * Filter function used by {@link getLogs}
 +	 * @param array element to be filtered
 +	 */
  	private function filterByCategories($value)
  	{
  		foreach($this->_categories as $category)
 @@ -45,6 +114,10 @@ class TLogger extends TComponent  		return false;
  	}
 +	/**
 +	 * Filter function used by {@link getLogs}
 +	 * @param array element to be filtered
 +	 */
  	private function filterByLevels($value)
  	{
  		if($value[1] & $this->_levels)
 diff --git a/framework/core.php b/framework/core.php index 4c72fca6..13d8318f 100644 --- a/framework/core.php +++ b/framework/core.php @@ -715,8 +715,21 @@ class PradoBase  		return $language;
  	}
 +	/**
 +	 * Writes a log message.
 +	 * This method wraps {@link log()} by checking the application mode.
 +	 * When the application is in Debug mode, debug backtrace information is appended
 +	 * to the message and the message is logged at DEBUG level.
 +	 * When the application is in Performance mode, this method does nothing.
 +	 * Otherwise, the message is logged at INFO level.
 +	 * @param string message to be logged
 +	 * @param string category of the message
 +	 * @see log, getLogger
 +	 */
  	public static function trace($msg,$category='Uncategorized')
  	{
 +		if(self::$_application && self::$_application->getMode()==='Performance')
 +			return;
  		if(!self::$_application || self::$_application->getMode()==='Debug')
  		{
  			$trace=debug_backtrace();
 @@ -729,6 +742,17 @@ class PradoBase  		self::log($msg,$level,$category);
  	}
 +	/**
 +	 * Logs a message.
 +	 * Messages logged by this method may be retrieved via {@link TLogger::getLogs}
 +	 * and may be recorded in different media, such as file, email, database, using
 +	 * {@link TLogRouter}.
 +	 * @param string message to be logged
 +	 * @param integer level of the message. Valid values include
 +	 * TLogger::DEBUG, TLogger::INFO, TLogger::NOTICE, TLogger::WARNING,
 +	 * TLogger::ERROR, TLogger::ALERT, TLogger::FATAL.
 +	 * @param string category of the message
 +	 */
  	public static function log($msg,$level=TLogger::INFO,$category='Uncategorized')
  	{
  		if(self::$_logger===null)
 @@ -736,6 +760,9 @@ class PradoBase  		self::$_logger->log($msg,$level,$category);
  	}
 +	/**
 +	 * @return TLogger message logger
 +	 */
  	public static function getLogger()
  	{
  		if(self::$_logger===null)
 | 
