summaryrefslogtreecommitdiff
path: root/framework/logging
diff options
context:
space:
mode:
Diffstat (limited to 'framework/logging')
-rw-r--r--framework/logging/CDbLogRoute.php156
-rw-r--r--framework/logging/CEmailLogRoute.php151
-rw-r--r--framework/logging/CFileLogRoute.php172
-rw-r--r--framework/logging/CLogFilter.php106
-rw-r--r--framework/logging/CLogRoute.php113
-rw-r--r--framework/logging/CLogRouter.php127
-rw-r--r--framework/logging/CLogger.php302
-rw-r--r--framework/logging/CProfileLogRoute.php202
-rw-r--r--framework/logging/CWebLogRoute.php67
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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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