Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** TLogRouter, TLogRoute, TFileLogRoute, TEmailLogRoute class file** @author Qiang Xue <qiang.xue@gmail.com>* @link http://www.pradosoft.com/* @copyright Copyright © 2005-2008 PradoSoft* @license http://www.pradosoft.com/license/* @version $Id: TLogRouter.php 2563 2008-11-11 10:03:41Z haertl.mike $* @package System.Util*/Prado::using('System.Data.TDbConnection');/*** 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 $Id: TLogRouter.php 2563 2008-11-11 10:03:41Z haertl.mike $* @package System.Util* @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){if(is_file($this->_configFile)){$dom=new TXmlDocument;$dom->loadFromFile($this->_configFile);$this->loadConfig($dom);}elsethrow new TConfigurationException('logrouter_configfile_invalid',$this->_configFile);}$this->loadConfig($config);$this->getApplication()->attachEventHandler('OnEndRequest',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){$properties=$routeConfig->getAttributes();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);}}/*** Adds a TLogRoute instance to the log router.** @param TLogRoute $route* @throws TInvalidDataTypeException if the route object is invalid*/public function addRoute($route){if(!($route instanceof TLogRoute))throw new TInvalidDataTypeException('logrouter_routetype_invalid');$this->_routes[]=$route;$route->init(null);}/*** @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 TConfigurationException if the file is invalid.*/public function setConfigFile($value){if(($this->_configFile=Prado::getPathOfNamespace($value,self::CONFIG_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($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 $Id: TLogRouter.php 2563 2008-11-11 10:03:41Z haertl.mike $* @package System.Util* @since 3.0*/abstract class TLogRoute extends TApplicationComponent{/*** @var array lookup table for level names*/protected static $_levelNames=array(TLogger::DEBUG=>'Debug',TLogger::INFO=>'Info',TLogger::NOTICE=>'Notice',TLogger::WARNING=>'Warning',TLogger::ERROR=>'Error',TLogger::ALERT=>'Alert',TLogger::FATAL=>'Fatal');/*** @var array lookup table for level values*/protected static $_levelValues=array('debug'=>TLogger::DEBUG,'info'=>TLogger::INFO,'notice'=>TLogger::NOTICE,'warning'=>TLogger::WARNING,'error'=>TLogger::ERROR,'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 'Debug', 'Info', 'Notice', 'Warning', 'Error', 'Alert' and 'Fatal'.*/public function setLevels($levels){if(is_integer($levels))$this->_levels=$levels;else{$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){if(is_array($categories))$this->_categories=$categories;else{$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 @gmdate('M d H:i:s',$time).' ['.$this->getLevelName($level).'] ['.$category.'] '.$message."\n";}/*** Retrieves log messages from logger to log route specific destination.* @param TLogger logger instance*/public function collectLogs(TLogger $logger){$logs=$logger->getLogs($this->getLevels(),$this->getCategories());if(!empty($logs))$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 elements represents one message* with the following structure:* array(* [0] => message* [1] => level* [2] => category* [3] => timestamp);*/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 $Id: TLogRouter.php 2563 2008-11-11 10:03:41Z haertl.mike $* @package System.Util* @since 3.0*/class TFileLogRoute extends TLogRoute{/*** @var integer maximum log file size*/private $_maxFileSize=512; // 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';/*** @return string directory storing log files. Defaults to application runtime path.*/public function getLogPath(){if($this->_logPath===null)$this->_logPath=$this->getApplication()->getRuntimePath();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 || !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)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);if($this->_maxLogFiles<1)throw new TInvalidDataValueException('filelogroute_maxlogfiles_invalid');}/*** Saves log messages in files.* @param array list of log messages*/protected function processLogs($logs){$logFile=$this->getLogPath().DIRECTORY_SEPARATOR.$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->getLogPath().DIRECTORY_SEPARATOR.$this->getLogFile();for($i=$this->_maxLogFiles;$i>0;--$i){$rotateFile=$file.'.'.$i;if(is_file($rotateFile)){if($i===$this->_maxLogFiles)unlink($rotateFile);elserename($rotateFile,$file.'.'.($i+1));}}if(is_file($file))rename($file,$file.'.1');}}/*** 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 $Id: TLogRouter.php 2563 2008-11-11 10:03:41Z haertl.mike $* @package System.Util* @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='';foreach($logs as $log)$message.=$this->formatLogMessage($log[0],$log[1],$log[2],$log[3]);$message=wordwrap($message,70);$returnPath = ini_get('sendmail_path') ? "Return-Path:{$this->_from}\r\n" : '';foreach($this->_emails as $email)mail($email,$this->getSubject(),$message,"From:{$this->_from}\r\n{$returnPath}");}/*** @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){if(is_array($emails))$this->_emails=$emails;else{$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;}/*** @return string send from address of the email*/public function getSentFrom(){return $this->_from;}/*** @param string send from address of the email*/public function setSentFrom($value){$this->_from=$value;}}/*** TBrowserLogRoute class.** TBrowserLogRoute prints selected log messages in the response.** @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>* @version $Id: TLogRouter.php 2563 2008-11-11 10:03:41Z haertl.mike $* @package System.Util* @since 3.0*/class TBrowserLogRoute extends TLogRoute{public function processLogs($logs){if(empty($logs) || $this->getApplication()->getMode()==='Performance') return;$first = $logs[0][3];$even = true;$response = $this->getApplication()->getResponse();$response->write($this->renderHeader());for($i=0,$n=count($logs);$i<$n;++$i){if ($i<$n-1){$timing['delta'] = $logs[$i+1][3] - $logs[$i][3];$timing['total'] = $logs[$i+1][3] - $first;}else{$timing['delta'] = '?';$timing['total'] = $logs[$i][3] - $first;}$timing['even'] = !($even = !$even);$response->write($this->renderMessage($logs[$i],$timing));}$response->write($this->renderFooter());}protected function renderHeader(){$string = <<<EOD<table cellspacing="0" cellpadding="2" border="0" width="100%"><tr><th style="background-color: black; color:white;" colspan="11">Application Log</th></tr><tr style="background-color: #ccc;"><th> </th><th>Category</th><th>Message</th><th>Time Spent (s)</th><th>Cumulated Time Spent (s)</th></tr>EOD;return $string;}protected function renderMessage($log, $info){$bgcolor = $info['even'] ? "#fff" : "#eee";$total = sprintf('%0.6f', $info['total']);$delta = sprintf('%0.6f', $info['delta']);$color = $this->getColorLevel($log[1]);$msg = preg_replace('/\(line[^\)]+\)$/','',$log[0]); //remove line number info$msg = THttpUtility::htmlEncode($msg);$string = <<<EOD<tr style="background-color: {$bgcolor};"><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">{$total}</td></tr>EOD;return $string;}protected function getColorLevel($level){switch($level){case TLogger::DEBUG: return 'green';case TLogger::INFO: return 'black';case TLogger::NOTICE: return '#3333FF';case TLogger::WARNING: return '#33FFFF';case TLogger::ERROR: return '#ff9933';case TLogger::ALERT: return '#ff00ff';case TLogger::FATAL: return 'red';}return '';}protected function renderFooter(){$string = "<tr><td colspan=\"11\" style=\"text-align:center; border-top: 1px solid #ccc; padding:0.2em;\">";foreach(self::$_levelValues as $name => $level){$string .= "<span style=\"color:white;background-color:".$this->getColorLevel($level);$string .= ";margin: 0.5em;\">".strtoupper($name)."</span>";}$string .= "</td></tr></table>";return $string;}}/*** TDbLogRoute class** TDbLogRoute stores log messages in a database table.* To specify the database table, set {@link setConnectionID ConnectionID} to be* the ID of a {@link TDataSourceConfig} module and {@link setLogTableName LogTableName}.* If they are not setting, an SQLite3 database named 'sqlite3.log' will be created and used* under the runtime directory.** By default, the database table name is 'pradolog'. It has the following structure:* <code>* CREATE TABLE pradolog* (* log_id INTEGER NOT NULL PRIMARY KEY,* level INTEGER,* category VARCHAR(128),* logtime VARCHAR(20),* message VARCHAR(255)* );* </code>** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TLogRouter.php 2563 2008-11-11 10:03:41Z haertl.mike $* @package System.Util* @since 3.1.2*/class TDbLogRoute extends TLogRoute{/*** @var string the ID of TDataSourceConfig module*/private $_connID='';/*** @var TDbConnection the DB connection instance*/private $_db;/*** @var string name of the DB log table*/private $_logTable='pradolog';/*** @var boolean whether the log DB table should be created automatically*/private $_autoCreate=true;/*** Destructor.* Disconnect the db connection.*/public function __destruct(){if($this->_db!==null)$this->_db->setActive(false);}/*** Initializes this module.* This method is required by the IModule interface.* It initializes the database for logging purpose.* @param TXmlElement configuration for this module, can be null* @throws TConfigurationException if the DB table does not exist.*/public function init($config){$db=$this->getDbConnection();$db->setActive(true);$sql='SELECT * FROM '.$this->_logTable.' WHERE 0';try{$db->createCommand($sql)->execute();}catch(Exception $e){// DB table not existsif($this->_autoCreate)$this->createDbTable();elsethrow new TConfigurationException('db_logtable_inexistent',$this->_logTable);}parent::init($config);}/*** Stores log messages into database.* @param array list of log messages*/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(':level',$log[0]);$command->bindValue(':category',$log[1]);$command->bindValue(':logtime',$log[2]);$command->bindValue(':message',$log[3]);$command->execute();}}/*** Creates the DB table for storing log messages.*/protected function createDbTable(){$sql='CREATE TABLE '.$this->_logTable.' (log_id INTEGER NOT NULL PRIMARY KEY,level INTEGER,category VARCHAR(128),logtime VARCHAR(20),message VARCHAR(255))';$this->getDbConnection()->createCommand($sql)->execute();}/*** Creates the DB connection.* @param string the module ID for TDataSourceConfig* @return TDbConnection the created DB connection* @throws TConfigurationException if module ID is invalid or empty*/protected function createDbConnection(){if($this->_connID!==''){$config=$this->getApplication()->getModule($this->_connID);if($config instanceof TDataSourceConfig)return $config->getDbConnection();elsethrow new TConfigurationException('dblogroute_connectionid_invalid',$this->_connID);}else{$db=new TDbConnection;// default to SQLite3 database$dbFile=$this->getApplication()->getRuntimePath().'/sqlite3.log';$db->setConnectionString('sqlite:'.$dbFile);return $db;}}/*** @return TDbConnection the DB connection instance*/public function getDbConnection(){if($this->_db===null)$this->_db=$this->createDbConnection();return $this->_db;}/*** @return string the ID of a {@link TDataSourceConfig} module. Defaults to empty string, meaning not set.*/public function getConnectionID(){return $this->_connID;}/*** Sets the ID of a TDataSourceConfig module.* The datasource module will be used to establish the DB connection for this log route.* @param string ID of the {@link TDataSourceConfig} module*/public function setConnectionID($value){$this->_connID=$value;}/*** @return string the name of the DB table to store log content. Defaults to 'pradolog'.* @see setAutoCreateLogTable*/public function getLogTableName(){return $this->_logTable;}/*** Sets the name of the DB table to store log content.* Note, if {@link setAutoCreateLogTable AutoCreateLogTable} is false* and you want to create the DB table manually by yourself,* you need to make sure the DB table is of the following structure:* (key CHAR(128) PRIMARY KEY, value BLOB, expire INT)* @param string the name of the DB table to store log content* @see setAutoCreateLogTable*/public function setLogTableName($value){$this->_logTable=$value;}/*** @return boolean whether the log DB table should be automatically created if not exists. Defaults to true.* @see setAutoCreateLogTable*/public function getAutoCreateLogTable(){return $this->_autoCreate;}/*** @param boolean whether the log DB table should be automatically created if not exists.* @see setLogTableName*/public function setAutoCreateLogTable($value){$this->_autoCreate=TPropertyValue::ensureBoolean($value);}}/*** TFirebugLogRoute class.** TFirebugLogRoute prints selected log messages in the firebug log console.** {@link http://www.getfirebug.com/ FireBug Website}** @author Enrico Stahn <mail@enricostahn.com>, Christophe Boulain <Christophe.Boulain@gmail.com>* @version $Id: TLogRouter.php 2563 2008-11-11 10:03:41Z haertl.mike $* @package System.Util* @since 3.1.2*/class TFirebugLogRoute extends TBrowserLogRoute{protected function renderHeader (){$string = <<<EOD<script type="text/javascript">/*<![CDATA[*/if (typeof(console) == 'object'){console.log ("[Cumulated Time] [Time] [Level] [Category] [Message]");EOD;return $string;}protected function renderMessage ($log, $info){$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],''));$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";return $string;}protected function renderFooter (){$string = <<<EOD}</script>EOD;return $string;}protected function getFirebugLoggingFunction($level){switch ($level){case TLogger::DEBUG:case TLogger::INFO:case TLogger::NOTICE:return 'console.log';case TLogger::WARNING:return 'console.warn';case TLogger::ERROR:case TLogger::ALERT:case TLogger::FATAL:return 'console.error';}return 'console.log';}}?>