diff options
Diffstat (limited to 'framework/logging')
| -rw-r--r-- | framework/logging/CDbLogRoute.php | 156 | ||||
| -rw-r--r-- | framework/logging/CEmailLogRoute.php | 151 | ||||
| -rw-r--r-- | framework/logging/CFileLogRoute.php | 172 | ||||
| -rw-r--r-- | framework/logging/CLogFilter.php | 106 | ||||
| -rw-r--r-- | framework/logging/CLogRoute.php | 113 | ||||
| -rw-r--r-- | framework/logging/CLogRouter.php | 127 | ||||
| -rw-r--r-- | framework/logging/CLogger.php | 302 | ||||
| -rw-r--r-- | framework/logging/CProfileLogRoute.php | 202 | ||||
| -rw-r--r-- | framework/logging/CWebLogRoute.php | 67 |
9 files changed, 1396 insertions, 0 deletions
diff --git a/framework/logging/CDbLogRoute.php b/framework/logging/CDbLogRoute.php new file mode 100644 index 0000000..8efbfae --- /dev/null +++ b/framework/logging/CDbLogRoute.php @@ -0,0 +1,156 @@ +<?php +/** + * CDbLogRoute class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * CDbLogRoute stores log messages in a database table. + * + * To specify the database table for storing log messages, set {@link logTableName} as + * the name of the table and specify {@link connectionID} to be the ID of a {@link CDbConnection} + * application component. If they are not set, a SQLite3 database named 'log-YiiVersion.db' will be created + * and used under the application runtime directory. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CDbLogRoute.php 3069 2011-03-14 00:28:38Z qiang.xue $ + * @package system.logging + * @since 1.0 + */ +class CDbLogRoute extends CLogRoute +{ + /** + * @var string the ID of CDbConnection application component. If not set, a SQLite database + * will be automatically created and used. The SQLite database file is + * <code>protected/runtime/log-YiiVersion.db</code>. + */ + public $connectionID; + /** + * @var string the name of the DB table that stores log content. Defaults to 'YiiLog'. + * If {@link 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: + * <pre> + * ( + * id INTEGER NOT NULL PRIMARY KEY, + * level VARCHAR(128), + * category VARCHAR(128), + * logtime INTEGER, + * message TEXT + * ) + * </pre> + * Note, the 'id' column must be created as an auto-incremental column. + * In MySQL, this means it should be <code>id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY</code>; + * In PostgreSQL, it is <code>id SERIAL PRIMARY KEY</code>. + * @see autoCreateLogTable + */ + public $logTableName='YiiLog'; + /** + * @var boolean whether the log DB table should be automatically created if not exists. Defaults to true. + * @see logTableName + */ + public $autoCreateLogTable=true; + /** + * @var CDbConnection the DB connection instance + */ + private $_db; + + /** + * Initializes the route. + * This method is invoked after the route is created by the route manager. + */ + public function init() + { + parent::init(); + + if($this->autoCreateLogTable) + { + $db=$this->getDbConnection(); + $sql="DELETE FROM {$this->logTableName} WHERE 0=1"; + try + { + $db->createCommand($sql)->execute(); + } + catch(Exception $e) + { + $this->createLogTable($db,$this->logTableName); + } + } + } + + /** + * Creates the DB table for storing log messages. + * @param CDbConnection $db the database connection + * @param string $tableName the name of the table to be created + */ + protected function createLogTable($db,$tableName) + { + $driver=$db->getDriverName(); + if($driver==='mysql') + $logID='id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY'; + else if($driver==='pgsql') + $logID='id SERIAL PRIMARY KEY'; + else + $logID='id INTEGER NOT NULL PRIMARY KEY'; + + $sql=" +CREATE TABLE $tableName +( + $logID, + level VARCHAR(128), + category VARCHAR(128), + logtime INTEGER, + message TEXT +)"; + $db->createCommand($sql)->execute(); + } + + /** + * @return CDbConnection the DB connection instance + * @throws CException if {@link connectionID} does not point to a valid application component. + */ + protected function getDbConnection() + { + if($this->_db!==null) + return $this->_db; + else if(($id=$this->connectionID)!==null) + { + if(($this->_db=Yii::app()->getComponent($id)) instanceof CDbConnection) + return $this->_db; + else + throw new CException(Yii::t('yii','CDbLogRoute.connectionID "{id}" does not point to a valid CDbConnection application component.', + array('{id}'=>$id))); + } + else + { + $dbFile=Yii::app()->getRuntimePath().DIRECTORY_SEPARATOR.'log-'.Yii::getVersion().'.db'; + return $this->_db=new CDbConnection('sqlite:'.$dbFile); + } + } + + /** + * Stores log messages into database. + * @param array $logs list of log messages + */ + protected function processLogs($logs) + { + $sql=" +INSERT INTO {$this->logTableName} +(level, category, logtime, message) VALUES +(:level, :category, :logtime, :message) +"; + $command=$this->getDbConnection()->createCommand($sql); + foreach($logs as $log) + { + $command->bindValue(':level',$log[1]); + $command->bindValue(':category',$log[2]); + $command->bindValue(':logtime',(int)$log[3]); + $command->bindValue(':message',$log[0]); + $command->execute(); + } + } +} diff --git a/framework/logging/CEmailLogRoute.php b/framework/logging/CEmailLogRoute.php new file mode 100644 index 0000000..3e146d6 --- /dev/null +++ b/framework/logging/CEmailLogRoute.php @@ -0,0 +1,151 @@ +<?php +/** + * CEmailLogRoute class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CEmailLogRoute 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}, the + * {@link setSentFrom sentFrom} address and any additional {@link setHeaders headers}. + * + * @property array $emails List of destination email addresses. + * @property string $subject Email subject. Defaults to CEmailLogRoute::DEFAULT_SUBJECT. + * @property string $sentFrom Send from address of the email. + * @property array $headers Additional headers to use when sending an email. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CEmailLogRoute.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @package system.logging + * @since 1.0 + */ +class CEmailLogRoute extends CLogRoute +{ + /** + * @var array list of destination email addresses. + */ + private $_email=array(); + /** + * @var string email subject + */ + private $_subject; + /** + * @var string email sent from address + */ + private $_from; + /** + * @var array list of additional headers to use when sending an email. + */ + private $_headers=array(); + + /** + * Sends log messages to specified email addresses. + * @param array $logs 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); + $subject=$this->getSubject(); + if($subject===null) + $subject=Yii::t('yii','Application Log'); + foreach($this->getEmails() as $email) + $this->sendEmail($email,$subject,$message); + } + + /** + * Sends an email. + * @param string $email single email address + * @param string $subject email subject + * @param string $message email content + */ + protected function sendEmail($email,$subject,$message) + { + $headers=$this->getHeaders(); + if(($from=$this->getSentFrom())!==null) + $headers[]="From: {$from}"; + mail($email,$subject,$message,implode("\r\n",$headers)); + } + + /** + * @return array list of destination email addresses + */ + public function getEmails() + { + return $this->_email; + } + + /** + * @param mixed $value list of destination email addresses. If the value is + * a string, it is assumed to be comma-separated email addresses. + */ + public function setEmails($value) + { + if(is_array($value)) + $this->_email=$value; + else + $this->_email=preg_split('/[\s,]+/',$value,-1,PREG_SPLIT_NO_EMPTY); + } + + /** + * @return string email subject. Defaults to CEmailLogRoute::DEFAULT_SUBJECT + */ + public function getSubject() + { + return $this->_subject; + } + + /** + * @param string $value 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 $value send from address of the email + */ + public function setSentFrom($value) + { + $this->_from=$value; + } + + /** + * @return array additional headers to use when sending an email. + * @since 1.1.4 + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * @param mixed $value list of additional headers to use when sending an email. + * If the value is a string, it is assumed to be line break separated headers. + * @since 1.1.4 + */ + public function setHeaders($value) + { + if (is_array($value)) + $this->_headers=$value; + else + $this->_headers=preg_split('/\r\n|\n/',$value,-1,PREG_SPLIT_NO_EMPTY); + } +}
\ No newline at end of file diff --git a/framework/logging/CFileLogRoute.php b/framework/logging/CFileLogRoute.php new file mode 100644 index 0000000..e9d0639 --- /dev/null +++ b/framework/logging/CFileLogRoute.php @@ -0,0 +1,172 @@ +<?php +/** + * CFileLogRoute class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CFileLogRoute 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. + * + * @property string $logPath Directory storing log files. Defaults to application runtime path. + * @property string $logFile Log file name. Defaults to 'application.log'. + * @property integer $maxFileSize Maximum log file size in kilo-bytes (KB). Defaults to 1024 (1MB). + * @property integer $maxLogFiles Number of files used for rotation. Defaults to 5. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CFileLogRoute.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @package system.logging + * @since 1.0 + */ +class CFileLogRoute extends CLogRoute +{ + /** + * @var integer maximum log file size + */ + private $_maxFileSize=1024; // in KB + /** + * @var integer number of log files used for rotation + */ + private $_maxLogFiles=5; + /** + * @var string directory storing log files + */ + private $_logPath; + /** + * @var string log file name + */ + private $_logFile='application.log'; + + + /** + * Initializes the route. + * This method is invoked after the route is created by the route manager. + */ + public function init() + { + parent::init(); + if($this->getLogPath()===null) + $this->setLogPath(Yii::app()->getRuntimePath()); + } + + /** + * @return string directory storing log files. Defaults to application runtime path. + */ + public function getLogPath() + { + return $this->_logPath; + } + + /** + * @param string $value directory for storing log files. + * @throws CException if the path is invalid + */ + public function setLogPath($value) + { + $this->_logPath=realpath($value); + if($this->_logPath===false || !is_dir($this->_logPath) || !is_writable($this->_logPath)) + throw new CException(Yii::t('yii','CFileLogRoute.logPath "{path}" does not point to a valid directory. Make sure the directory exists and is writable by the Web server process.', + array('{path}'=>$value))); + } + + /** + * @return string log file name. Defaults to 'application.log'. + */ + public function getLogFile() + { + return $this->_logFile; + } + + /** + * @param string $value 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 $value maximum log file size in kilo-bytes (KB). + */ + public function setMaxFileSize($value) + { + if(($this->_maxFileSize=(int)$value)<1) + $this->_maxFileSize=1; + } + + /** + * @return integer number of files used for rotation. Defaults to 5. + */ + public function getMaxLogFiles() + { + return $this->_maxLogFiles; + } + + /** + * @param integer $value number of files used for rotation. + */ + public function setMaxLogFiles($value) + { + if(($this->_maxLogFiles=(int)$value)<1) + $this->_maxLogFiles=1; + } + + /** + * Saves log messages in files. + * @param array $logs list of log messages + */ + protected function processLogs($logs) + { + $logFile=$this->getLogPath().DIRECTORY_SEPARATOR.$this->getLogFile(); + if(@filesize($logFile)>$this->getMaxFileSize()*1024) + $this->rotateFiles(); + $fp=@fopen($logFile,'a'); + @flock($fp,LOCK_EX); + foreach($logs as $log) + @fwrite($fp,$this->formatLogMessage($log[0],$log[1],$log[2],$log[3])); + @flock($fp,LOCK_UN); + @fclose($fp); + } + + /** + * Rotates log files. + */ + protected function rotateFiles() + { + $file=$this->getLogPath().DIRECTORY_SEPARATOR.$this->getLogFile(); + $max=$this->getMaxLogFiles(); + for($i=$max;$i>0;--$i) + { + $rotateFile=$file.'.'.$i; + if(is_file($rotateFile)) + { + // suppress errors because it's possible multiple processes enter into this section + if($i===$max) + @unlink($rotateFile); + else + @rename($rotateFile,$file.'.'.($i+1)); + } + } + if(is_file($file)) + @rename($file,$file.'.1'); // suppress errors because it's possible multiple processes enter into this section + } +} diff --git a/framework/logging/CLogFilter.php b/framework/logging/CLogFilter.php new file mode 100644 index 0000000..a446650 --- /dev/null +++ b/framework/logging/CLogFilter.php @@ -0,0 +1,106 @@ +<?php +/** + * CLogFilter class file + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CLogFilter preprocesses the logged messages before they are handled by a log route. + * + * CLogFilter is meant to be used by a log route to preprocess the logged messages + * before they are handled by the route. The default implementation of CLogFilter + * prepends additional context information to the logged messages. In particular, + * by setting {@link logVars}, predefined PHP variables such as + * $_SERVER, $_POST, etc. can be saved as a log message, which may help identify/debug + * issues encountered. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CLogFilter.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.logging + */ +class CLogFilter extends CComponent +{ + /** + * @var boolean whether to prefix each log message with the current user session ID. + * Defaults to false. + */ + public $prefixSession=false; + /** + * @var boolean whether to prefix each log message with the current user + * {@link CWebUser::name name} and {@link CWebUser::id ID}. Defaults to false. + */ + public $prefixUser=false; + /** + * @var boolean whether to log the current user name and ID. Defaults to true. + */ + public $logUser=true; + /** + * @var array list of the PHP predefined variables that should be logged. + * Note that a variable must be accessible via $GLOBALS. Otherwise it won't be logged. + */ + public $logVars=array('_GET','_POST','_FILES','_COOKIE','_SESSION','_SERVER'); + + + /** + * Filters the given log messages. + * This is the main method of CLogFilter. It processes the log messages + * by adding context information, etc. + * @param array $logs the log messages + * @return array + */ + public function filter(&$logs) + { + if (!empty($logs)) + { + if(($message=$this->getContext())!=='') + array_unshift($logs,array($message,CLogger::LEVEL_INFO,'application',YII_BEGIN_TIME)); + $this->format($logs); + } + return $logs; + } + + /** + * Formats the log messages. + * The default implementation will prefix each message with session ID + * if {@link prefixSession} is set true. It may also prefix each message + * with the current user's name and ID if {@link prefixUser} is true. + * @param array $logs the log messages + */ + protected function format(&$logs) + { + $prefix=''; + if($this->prefixSession && ($id=session_id())!=='') + $prefix.="[$id]"; + if($this->prefixUser && ($user=Yii::app()->getComponent('user',false))!==null) + $prefix.='['.$user->getName().']['.$user->getId().']'; + if($prefix!=='') + { + foreach($logs as &$log) + $log[0]=$prefix.' '.$log[0]; + } + } + + /** + * Generates the context information to be logged. + * The default implementation will dump user information, system variables, etc. + * @return string the context information. If an empty string, it means no context information. + */ + protected function getContext() + { + $context=array(); + if($this->logUser && ($user=Yii::app()->getComponent('user',false))!==null) + $context[]='User: '.$user->getName().' (ID: '.$user->getId().')'; + + foreach($this->logVars as $name) + { + if(!empty($GLOBALS[$name])) + $context[]="\${$name}=".var_export($GLOBALS[$name],true); + } + + return implode("\n\n",$context); + } +}
\ No newline at end of file diff --git a/framework/logging/CLogRoute.php b/framework/logging/CLogRoute.php new file mode 100644 index 0000000..41e1ad4 --- /dev/null +++ b/framework/logging/CLogRoute.php @@ -0,0 +1,113 @@ +<?php +/** + * CLogRoute class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CLogRoute 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 levels} property, + * which takes a string of comma-separated desired level names (e.g. 'Error, Debug'). + * To specify category filter, set {@link 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: CLogRoute.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.logging + * @since 1.0 + */ +abstract class CLogRoute extends CComponent +{ + /** + * @var boolean whether to enable this log route. Defaults to true. + */ + public $enabled=true; + /** + * @var string list of levels separated by comma or space. Defaults to empty, meaning all levels. + */ + public $levels=''; + /** + * @var string list of categories separated by comma or space. Defaults to empty, meaning all categories. + */ + public $categories=''; + /** + * @var mixed the additional filter (eg {@link CLogFilter}) that can be applied to the log messages. + * The value of this property will be passed to {@link Yii::createComponent} to create + * a log filter object. As a result, this can be either a string representing the + * filter class name or an array representing the filter configuration. + * In general, the log filter class should be {@link CLogFilter} or a child class of it. + * Defaults to null, meaning no filter will be used. + */ + public $filter; + /** + * @var array the logs that are collected so far by this log route. + * @since 1.1.0 + */ + public $logs; + + + /** + * Initializes the route. + * This method is invoked after the route is created by the route manager. + */ + public function init() + { + } + + /** + * Formats a log message given different fields. + * @param string $message message content + * @param integer $level message level + * @param string $category message category + * @param integer $time timestamp + * @return string formatted message + */ + protected function formatLogMessage($message,$level,$category,$time) + { + return @date('Y/m/d H:i:s',$time)." [$level] [$category] $message\n"; + } + + /** + * Retrieves filtered log messages from logger for further processing. + * @param CLogger $logger logger instance + * @param boolean $processLogs whether to process the logs after they are collected from the logger + */ + public function collectLogs($logger, $processLogs=false) + { + $logs=$logger->getLogs($this->levels,$this->categories); + $this->logs=empty($this->logs) ? $logs : array_merge($this->logs,$logs); + if($processLogs && !empty($this->logs)) + { + if($this->filter!==null) + Yii::createComponent($this->filter)->filter($this->logs); + $this->processLogs($this->logs); + $this->logs=array(); + } + } + + /** + * Processes log messages and sends them to specific destination. + * Derived child classes must implement this method. + * @param array $logs list of messages. Each array elements represents one message + * with the following structure: + * array( + * [0] => message (string) + * [1] => level (string) + * [2] => category (string) + * [3] => timestamp (float, obtained by microtime(true)); + */ + abstract protected function processLogs($logs); +} diff --git a/framework/logging/CLogRouter.php b/framework/logging/CLogRouter.php new file mode 100644 index 0000000..4edbe48 --- /dev/null +++ b/framework/logging/CLogRouter.php @@ -0,0 +1,127 @@ +<?php +/** + * CLogRouter class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CLogRouter manages log routes that record log messages in different media. + * + * For example, a file log route {@link CFileLogRoute} records log messages + * in log files. An email log route {@link CEmailLogRoute} sends log messages + * to specific email addresses. See {@link CLogRoute} for more details about + * different log routes. + * + * Log routes may be configured in application configuration like following: + * <pre> + * array( + * 'preload'=>array('log'), // preload log component when app starts + * 'components'=>array( + * 'log'=>array( + * 'class'=>'CLogRouter', + * 'routes'=>array( + * array( + * 'class'=>'CFileLogRoute', + * 'levels'=>'trace, info', + * 'categories'=>'system.*', + * ), + * array( + * 'class'=>'CEmailLogRoute', + * 'levels'=>'error, warning', + * 'emails'=>array('admin@example.com'), + * ), + * ), + * ), + * ), + * ) + * </pre> + * + * You can specify multiple routes with different filtering conditions and different + * targets, even if the routes are of the same type. + * + * @property array $routes The currently initialized routes. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CLogRouter.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @package system.logging + * @since 1.0 + */ +class CLogRouter extends CApplicationComponent +{ + private $_routes=array(); + + /** + * Initializes this application component. + * This method is required by the IApplicationComponent interface. + */ + public function init() + { + parent::init(); + foreach($this->_routes as $name=>$route) + { + $route=Yii::createComponent($route); + $route->init(); + $this->_routes[$name]=$route; + } + Yii::getLogger()->attachEventHandler('onFlush',array($this,'collectLogs')); + Yii::app()->attachEventHandler('onEndRequest',array($this,'processLogs')); + } + + /** + * @return array the currently initialized routes + */ + public function getRoutes() + { + return new CMap($this->_routes); + } + + /** + * @param array $config list of route configurations. Each array element represents + * the configuration for a single route and has the following array structure: + * <ul> + * <li>class: specifies the class name or alias for the route class.</li> + * <li>name-value pairs: configure the initial property values of the route.</li> + * </ul> + */ + public function setRoutes($config) + { + foreach($config as $name=>$route) + $this->_routes[$name]=$route; + } + + /** + * Collects log messages from a logger. + * This method is an event handler to the {@link CLogger::onFlush} event. + * @param CEvent $event event parameter + */ + public function collectLogs($event) + { + $logger=Yii::getLogger(); + $dumpLogs=isset($event->params['dumpLogs']) && $event->params['dumpLogs']; + foreach($this->_routes as $route) + { + if($route->enabled) + $route->collectLogs($logger,$dumpLogs); + } + } + + /** + * Collects and processes log messages from a logger. + * This method is an event handler to the {@link CApplication::onEndRequest} event. + * @param CEvent $event event parameter + * @since 1.1.0 + */ + public function processLogs($event) + { + $logger=Yii::getLogger(); + foreach($this->_routes as $route) + { + if($route->enabled) + $route->collectLogs($logger,true); + } + } +} diff --git a/framework/logging/CLogger.php b/framework/logging/CLogger.php new file mode 100644 index 0000000..2851571 --- /dev/null +++ b/framework/logging/CLogger.php @@ -0,0 +1,302 @@ +<?php +/** + * CLogger class file + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CLogger records log messages in memory. + * + * CLogger implements the methods to retrieve the messages with + * various filter conditions, including log levels and log categories. + * + * @property array $logs List of messages. Each array elements represents one message + * with the following structure: + * array( + * [0] => message (string) + * [1] => level (string) + * [2] => category (string) + * [3] => timestamp (float, obtained by microtime(true));. + * @property float $executionTime The total time for serving the current request. + * @property integer $memoryUsage Memory usage of the application (in bytes). + * @property array $profilingResults The profiling results. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CLogger.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.logging + * @since 1.0 + */ +class CLogger extends CComponent +{ + const LEVEL_TRACE='trace'; + const LEVEL_WARNING='warning'; + const LEVEL_ERROR='error'; + const LEVEL_INFO='info'; + const LEVEL_PROFILE='profile'; + + /** + * @var integer how many messages should be logged before they are flushed to destinations. + * Defaults to 10,000, meaning for every 10,000 messages, the {@link flush} method will be + * automatically invoked once. If this is 0, it means messages will never be flushed automatically. + * @since 1.1.0 + */ + public $autoFlush=10000; + /** + * @var boolean this property will be passed as the parameter to {@link flush()} when it is + * called in {@link log()} due to the limit of {@link autoFlush} being reached. + * By default, this property is false, meaning the filtered messages are still kept in the memory + * by each log route after calling {@link flush()}. If this is true, the filtered messages + * will be written to the actual medium each time {@link flush()} is called within {@link log()}. + * @since 1.1.8 + */ + public $autoDump=false; + /** + * @var array log messages + */ + private $_logs=array(); + /** + * @var integer number of log messages + */ + private $_logCount=0; + /** + * @var array log levels for filtering (used when filtering) + */ + private $_levels; + /** + * @var array log categories for filtering (used when filtering) + */ + private $_categories; + /** + * @var array the profiling results (category, token => time in seconds) + */ + private $_timings; + /** + * @var boolean if we are processing the log or still accepting new log messages + * @since 1.1.9 + */ + private $_processing = false; + + /** + * Logs a message. + * Messages logged by this method may be retrieved back via {@link getLogs}. + * @param string $message message to be logged + * @param string $level level of the message (e.g. 'Trace', 'Warning', 'Error'). It is case-insensitive. + * @param string $category category of the message (e.g. 'system.web'). It is case-insensitive. + * @see getLogs + */ + public function log($message,$level='info',$category='application') + { + $this->_logs[]=array($message,$level,$category,microtime(true)); + $this->_logCount++; + if($this->autoFlush>0 && $this->_logCount>=$this->autoFlush && !$this->_processing) + { + $this->_processing=true; + $this->flush($this->autoDump); + $this->_processing=false; + } + } + + /** + * Retrieves log messages. + * + * Messages may be filtered by log levels and/or categories. + * A level filter is specified by a list of levels separated by comma or space + * (e.g. 'trace, error'). A category filter is similar to level filter + * (e.g. 'system, system.web'). A difference is that in category filter + * you can use pattern like 'system.*' to indicate all categories starting + * with 'system'. + * + * If you do not specify level filter, it will bring back logs at all levels. + * The same applies to category filter. + * + * Level filter and category filter are combinational, i.e., only messages + * satisfying both filter conditions will be returned. + * + * @param string $levels level filter + * @param string $categories category filter + * @return array list of messages. Each array elements represents one message + * with the following structure: + * array( + * [0] => message (string) + * [1] => level (string) + * [2] => category (string) + * [3] => timestamp (float, obtained by microtime(true)); + */ + public function getLogs($levels='',$categories='') + { + $this->_levels=preg_split('/[\s,]+/',strtolower($levels),-1,PREG_SPLIT_NO_EMPTY); + $this->_categories=preg_split('/[\s,]+/',strtolower($categories),-1,PREG_SPLIT_NO_EMPTY); + if(empty($levels) && empty($categories)) + return $this->_logs; + else if(empty($levels)) + return array_values(array_filter(array_filter($this->_logs,array($this,'filterByCategory')))); + else if(empty($categories)) + return array_values(array_filter(array_filter($this->_logs,array($this,'filterByLevel')))); + else + { + $ret=array_values(array_filter(array_filter($this->_logs,array($this,'filterByLevel')))); + return array_values(array_filter(array_filter($ret,array($this,'filterByCategory')))); + } + } + + /** + * Filter function used by {@link getLogs} + * @param array $value element to be filtered + * @return array valid log, false if not. + */ + private function filterByCategory($value) + { + foreach($this->_categories as $category) + { + $cat=strtolower($value[2]); + if($cat===$category || (($c=rtrim($category,'.*'))!==$category && strpos($cat,$c)===0)) + return $value; + } + return false; + } + + /** + * Filter function used by {@link getLogs} + * @param array $value element to be filtered + * @return array valid log, false if not. + */ + private function filterByLevel($value) + { + return in_array(strtolower($value[1]),$this->_levels)?$value:false; + } + + /** + * Returns the total time for serving the current request. + * This method calculates the difference between now and the timestamp + * defined by constant YII_BEGIN_TIME. + * To estimate the execution time more accurately, the constant should + * be defined as early as possible (best at the beginning of the entry script.) + * @return float the total time for serving the current request. + */ + public function getExecutionTime() + { + return microtime(true)-YII_BEGIN_TIME; + } + + /** + * Returns the memory usage of the current application. + * This method relies on the PHP function memory_get_usage(). + * If it is not available, the method will attempt to use OS programs + * to determine the memory usage. A value 0 will be returned if the + * memory usage can still not be determined. + * @return integer memory usage of the application (in bytes). + */ + public function getMemoryUsage() + { + if(function_exists('memory_get_usage')) + return memory_get_usage(); + else + { + $output=array(); + if(strncmp(PHP_OS,'WIN',3)===0) + { + exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST',$output); + return isset($output[5])?preg_replace('/[\D]/','',$output[5])*1024 : 0; + } + else + { + $pid=getmypid(); + exec("ps -eo%mem,rss,pid | grep $pid", $output); + $output=explode(" ",$output[0]); + return isset($output[1]) ? $output[1]*1024 : 0; + } + } + } + + /** + * Returns the profiling results. + * The results may be filtered by token and/or category. + * If no filter is specified, the returned results would be an array with each element + * being array($token,$category,$time). + * If a filter is specified, the results would be an array of timings. + * @param string $token token filter. Defaults to null, meaning not filtered by token. + * @param string $category category filter. Defaults to null, meaning not filtered by category. + * @param boolean $refresh whether to refresh the internal timing calculations. If false, + * only the first time calling this method will the timings be calculated internally. + * @return array the profiling results. + */ + public function getProfilingResults($token=null,$category=null,$refresh=false) + { + if($this->_timings===null || $refresh) + $this->calculateTimings(); + if($token===null && $category===null) + return $this->_timings; + $results=array(); + foreach($this->_timings as $timing) + { + if(($category===null || $timing[1]===$category) && ($token===null || $timing[0]===$token)) + $results[]=$timing[2]; + } + return $results; + } + + private function calculateTimings() + { + $this->_timings=array(); + + $stack=array(); + foreach($this->_logs as $log) + { + if($log[1]!==CLogger::LEVEL_PROFILE) + continue; + list($message,$level,$category,$timestamp)=$log; + if(!strncasecmp($message,'begin:',6)) + { + $log[0]=substr($message,6); + $stack[]=$log; + } + else if(!strncasecmp($message,'end:',4)) + { + $token=substr($message,4); + if(($last=array_pop($stack))!==null && $last[0]===$token) + { + $delta=$log[3]-$last[3]; + $this->_timings[]=array($message,$category,$delta); + } + else + throw new CException(Yii::t('yii','CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', + array('{token}'=>$token))); + } + } + + $now=microtime(true); + while(($last=array_pop($stack))!==null) + { + $delta=$now-$last[3]; + $this->_timings[]=array($last[0],$last[2],$delta); + } + } + + /** + * Removes all recorded messages from the memory. + * This method will raise an {@link onFlush} event. + * The attached event handlers can process the log messages before they are removed. + * @param boolean $dumpLogs whether to process the logs immediately as they are passed to log route + * @since 1.1.0 + */ + public function flush($dumpLogs=false) + { + $this->onFlush(new CEvent($this, array('dumpLogs'=>$dumpLogs))); + $this->_logs=array(); + $this->_logCount=0; + } + + /** + * Raises an <code>onFlush</code> event. + * @param CEvent $event the event parameter + * @since 1.1.0 + */ + public function onFlush($event) + { + $this->raiseEvent('onFlush', $event); + } +} diff --git a/framework/logging/CProfileLogRoute.php b/framework/logging/CProfileLogRoute.php new file mode 100644 index 0000000..f1d5a04 --- /dev/null +++ b/framework/logging/CProfileLogRoute.php @@ -0,0 +1,202 @@ +<?php +/** + * CProfileLogRoute class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CProfileLogRoute displays the profiling results in Web page. + * + * The profiling is done by calling {@link YiiBase::beginProfile()} and {@link YiiBase::endProfile()}, + * which marks the begin and end of a code block. + * + * CProfileLogRoute supports two types of report by setting the {@link setReport report} property: + * <ul> + * <li>summary: list the execution time of every marked code block</li> + * <li>callstack: list the mark code blocks in a hierarchical view reflecting their calling sequence.</li> + * </ul> + * + * @property string $report The type of the profiling report to display. Defaults to 'summary'. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CProfileLogRoute.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.logging + * @since 1.0 + */ +class CProfileLogRoute extends CWebLogRoute +{ + /** + * @var boolean whether to aggregate results according to profiling tokens. + * If false, the results will be aggregated by categories. + * Defaults to true. Note that this property only affects the summary report + * that is enabled when {@link report} is 'summary'. + */ + public $groupByToken=true; + /** + * @var string type of profiling report to display + */ + private $_report='summary'; + + /** + * Initializes the route. + * This method is invoked after the route is created by the route manager. + */ + public function init() + { + $this->levels=CLogger::LEVEL_PROFILE; + } + + /** + * @return string the type of the profiling report to display. Defaults to 'summary'. + */ + public function getReport() + { + return $this->_report; + } + + /** + * @param string $value the type of the profiling report to display. Valid values include 'summary' and 'callstack'. + */ + public function setReport($value) + { + if($value==='summary' || $value==='callstack') + $this->_report=$value; + else + throw new CException(Yii::t('yii','CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".', + array('{report}'=>$value))); + } + + /** + * Displays the log messages. + * @param array $logs list of log messages + */ + public function processLogs($logs) + { + $app=Yii::app(); + if(!($app instanceof CWebApplication) || $app->getRequest()->getIsAjaxRequest()) + return; + + if($this->getReport()==='summary') + $this->displaySummary($logs); + else + $this->displayCallstack($logs); + } + + /** + * Displays the callstack of the profiling procedures for display. + * @param array $logs list of logs + */ + protected function displayCallstack($logs) + { + $stack=array(); + $results=array(); + $n=0; + foreach($logs as $log) + { + if($log[1]!==CLogger::LEVEL_PROFILE) + continue; + $message=$log[0]; + if(!strncasecmp($message,'begin:',6)) + { + $log[0]=substr($message,6); + $log[4]=$n; + $stack[]=$log; + $n++; + } + else if(!strncasecmp($message,'end:',4)) + { + $token=substr($message,4); + if(($last=array_pop($stack))!==null && $last[0]===$token) + { + $delta=$log[3]-$last[3]; + $results[$last[4]]=array($token,$delta,count($stack)); + } + else + throw new CException(Yii::t('yii','CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', + array('{token}'=>$token))); + } + } + // remaining entries should be closed here + $now=microtime(true); + while(($last=array_pop($stack))!==null) + $results[$last[4]]=array($last[0],$now-$last[3],count($stack)); + ksort($results); + $this->render('profile-callstack',$results); + } + + /** + * Displays the summary report of the profiling result. + * @param array $logs list of logs + */ + protected function displaySummary($logs) + { + $stack=array(); + foreach($logs as $log) + { + if($log[1]!==CLogger::LEVEL_PROFILE) + continue; + $message=$log[0]; + if(!strncasecmp($message,'begin:',6)) + { + $log[0]=substr($message,6); + $stack[]=$log; + } + else if(!strncasecmp($message,'end:',4)) + { + $token=substr($message,4); + if(($last=array_pop($stack))!==null && $last[0]===$token) + { + $delta=$log[3]-$last[3]; + if(!$this->groupByToken) + $token=$log[2]; + if(isset($results[$token])) + $results[$token]=$this->aggregateResult($results[$token],$delta); + else + $results[$token]=array($token,1,$delta,$delta,$delta); + } + else + throw new CException(Yii::t('yii','CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', + array('{token}'=>$token))); + } + } + + $now=microtime(true); + while(($last=array_pop($stack))!==null) + { + $delta=$now-$last[3]; + $token=$this->groupByToken ? $last[0] : $last[2]; + if(isset($results[$token])) + $results[$token]=$this->aggregateResult($results[$token],$delta); + else + $results[$token]=array($token,1,$delta,$delta,$delta); + } + + $entries=array_values($results); + $func=create_function('$a,$b','return $a[4]<$b[4]?1:0;'); + usort($entries,$func); + + $this->render('profile-summary',$entries); + } + + /** + * Aggregates the report result. + * @param array $result log result for this code block + * @param float $delta time spent for this code block + * @return array + */ + protected function aggregateResult($result,$delta) + { + list($token,$calls,$min,$max,$total)=$result; + if($delta<$min) + $min=$delta; + else if($delta>$max) + $max=$delta; + $calls++; + $total+=$delta; + return array($token,$calls,$min,$max,$total); + } +}
\ No newline at end of file diff --git a/framework/logging/CWebLogRoute.php b/framework/logging/CWebLogRoute.php new file mode 100644 index 0000000..9e120d8 --- /dev/null +++ b/framework/logging/CWebLogRoute.php @@ -0,0 +1,67 @@ +<?php +/** + * CWebLogRoute class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CWebLogRoute shows the log content in Web page. + * + * The log content can appear either at the end of the current Web page + * or in FireBug console window (if {@link showInFireBug} is set true). + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CWebLogRoute.php 3001 2011-02-24 16:42:44Z alexander.makarow $ + * @package system.logging + * @since 1.0 + */ +class CWebLogRoute extends CLogRoute +{ + /** + * @var boolean whether the log should be displayed in FireBug instead of browser window. Defaults to false. + */ + public $showInFireBug=false; + + /** + * @var boolean whether the log should be ignored in FireBug for ajax calls. Defaults to true. + * This option should be used carefully, because an ajax call returns all output as a result data. + * For example if the ajax call expects a json type result any output from the logger will cause ajax call to fail. + */ + public $ignoreAjaxInFireBug=true; + + /** + * Displays the log messages. + * @param array $logs list of log messages + */ + public function processLogs($logs) + { + $this->render('log',$logs); + } + + /** + * Renders the view. + * @param string $view the view name (file name without extension). The file is assumed to be located under framework/data/views. + * @param array $data data to be passed to the view + */ + protected function render($view,$data) + { + $app=Yii::app(); + $isAjax=$app->getRequest()->getIsAjaxRequest(); + + if($this->showInFireBug) + { + if($isAjax && $this->ignoreAjaxInFireBug) + return; + $view.='-firebug'; + } + else if(!($app instanceof CWebApplication) || $isAjax) + return; + + $viewFile=YII_PATH.DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.$view.'.php'; + include($app->findLocalizedFile($viewFile,'en')); + } +}
\ No newline at end of file |
