diff --git a/framework/YiiBase.php b/framework/YiiBase.php
index fa097d4..bc5b2cc 100644
--- a/framework/YiiBase.php
+++ b/framework/YiiBase.php
@@ -30,11 +30,10 @@ defined('YII_ENABLE_EXCEPTION_HANDLER') or define('YII_ENABLE_EXCEPTION_HANDLER'
*/
defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', true);
/**
- * Defines the Yii framework installation path.
+ * This constant defines the framework installation directory.
*/
defined('YII_PATH') or define('YII_PATH', __DIR__);
-
/**
* YiiBase is the core helper class for the Yii framework.
*
@@ -66,7 +65,7 @@ class YiiBase
* @var array registered path aliases
*/
public static $aliases = array(
- '@yii' => YII_PATH,
+ '@yii' => __DIR__,
);
private static $_imported = array(); // alias => class name or directory
@@ -81,15 +80,6 @@ class YiiBase
}
/**
- * Returns the installation directory of the Yii framework.
- * @return string the path of the framework
- */
- public static function getFrameworkPath()
- {
- return YII_PATH;
- }
-
- /**
* Imports a class or a directory.
*
* Importing a class is like including the corresponding class file.
@@ -370,86 +360,83 @@ class YiiBase
}
/**
- * Writes a trace message.
- * This method will only log a message when the application is in debug mode.
- * @param string $msg message to be logged
- * @param string $category category of the message
- * @see log
+ * Logs a trace message.
+ * Trace messages are logged mainly for development purpose to see
+ * the execution work flow of some code.
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
*/
- public static function trace($msg, $category = 'application')
+ public static function trace($message, $category = 'application')
{
if (YII_DEBUG) {
- static::log($msg, CLogger::LEVEL_TRACE, $category);
+ self::getLogger()->trace($message, $category);
}
}
/**
- * Logs a message.
- * Messages logged by this method may be retrieved via {@link CLogger::getLogs}
- * and may be recorded in different media, such as file, email, database, using
- * {@link CLogRouter}.
- * @param string $msg 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.
+ * Logs an error message.
+ * An error message is typically logged when an unrecoverable error occurs
+ * during the execution of an application.
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
*/
- public static function log($msg, $level = CLogger::LEVEL_INFO, $category = 'application')
+ public function error($message, $category = 'application')
{
- if (self::$_logger === null) {
- self::$_logger = new CLogger;
- }
- if (YII_DEBUG && YII_TRACE_LEVEL > 0 && $level !== CLogger::LEVEL_PROFILE)
- {
- $traces = debug_backtrace();
- $count = 0;
- foreach ($traces as $trace)
- {
- if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII_PATH) !== 0)
- {
- $msg .= "\nin " . $trace['file'] . ' (' . $trace['line'] . ')';
- if (++$count >= YII_TRACE_LEVEL)
- break;
- }
- }
- }
- self::$_logger->log($msg, $level, $category);
+ self::getLogger()->error($message, $category);
}
/**
- * Marks the begin of a code block for profiling.
- * This has to be matched with a call to {@link endProfile()} with the same token.
- * The begin- and end- calls must also be properly nested, e.g.,
- *
- * Yii::beginProfile('block1');
- * Yii::beginProfile('block2');
- * Yii::endProfile('block2');
- * Yii::endProfile('block1');
- *
- * The following sequence is not valid:
- *
- * Yii::beginProfile('block1');
- * Yii::beginProfile('block2');
- * Yii::endProfile('block1');
- * Yii::endProfile('block2');
- *
- * @param string $token token for the code block
- * @param string $category the category of this log message
+ * Logs a warning message.
+ * A warning message is typically logged when an error occurs while the execution
+ * can still continue.
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
+ */
+ public function warn($message, $category = 'application')
+ {
+ self::getLogger()->warn($message, $category);
+ }
+
+ /**
+ * Logs an informative message.
+ * An informative message is typically logged by an application to keep record of
+ * something important (e.g. an administrator logs in).
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
+ */
+ public function info($message, $category = 'application')
+ {
+ self::getLogger()->info($message, $category);
+ }
+
+ /**
+ * Marks the beginning of a code block for profiling.
+ * This has to be matched with a call to [[endProfile]] with the same category name.
+ * The begin- and end- calls must also be properly nested. For example,
+ *
+ * ~~~
+ * \Yii::beginProfile('block1');
+ * \Yii::beginProfile('block2');
+ * \Yii::endProfile('block2');
+ * \Yii::endProfile('block1');
+ * ~~~
+ * @param string $category the category of this profile block
* @see endProfile
*/
- public static function beginProfile($token, $category = 'application')
+ public static function beginProfile($category)
{
- static::log('begin:' . $token, CLogger::LEVEL_PROFILE, $category);
+ self::getLogger()->beginProfile($category);
}
/**
* Marks the end of a code block for profiling.
- * This has to be matched with a previous call to {@link beginProfile()} with the same token.
- * @param string $token token for the code block
- * @param string $category the category of this log message
+ * This has to be matched with a previous call to [[beginProfile]] with the same category name.
+ * @param string $category the category of this profile block
* @see beginProfile
*/
- public static function endProfile($token, $category = 'application')
+ public static function endProfile($category)
{
- static::log('end:' . $token, CLogger::LEVEL_PROFILE, $category);
+ self::getLogger()->endProfile($category);
}
/**
diff --git a/framework/logging/DbTarget.php b/framework/logging/DbTarget.php
new file mode 100644
index 0000000..c1d4573
--- /dev/null
+++ b/framework/logging/DbTarget.php
@@ -0,0 +1,156 @@
+
+ * @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
+ * @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
+ * protected/runtime/log-YiiVersion.db
.
+ */
+ 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:
+ *
+ * (
+ * id INTEGER NOT NULL PRIMARY KEY,
+ * level VARCHAR(128),
+ * category VARCHAR(128),
+ * logtime INTEGER,
+ * message TEXT
+ * )
+ *
+ * Note, the 'id' column must be created as an auto-incremental column.
+ * In MySQL, this means it should be id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY
;
+ * In PostgreSQL, it is id SERIAL PRIMARY KEY
.
+ * @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';
+ elseif ($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;
+ elseif (($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/EmailTarget.php b/framework/logging/EmailTarget.php
new file mode 100644
index 0000000..c6a381a
--- /dev/null
+++ b/framework/logging/EmailTarget.php
@@ -0,0 +1,146 @@
+
+ * @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}.
+ *
+ * @author Qiang Xue
+ * @version $Id: CEmailLogRoute.php 3001 2011-02-24 16:42:44Z 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/FileTarget.php b/framework/logging/FileTarget.php
new file mode 100644
index 0000000..993cb42
--- /dev/null
+++ b/framework/logging/FileTarget.php
@@ -0,0 +1,167 @@
+
+ * @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.
+ *
+ * @author Qiang Xue
+ * @version $Id: CFileLogRoute.php 3001 2011-02-24 16:42:44Z 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/Filter.php b/framework/logging/Filter.php
new file mode 100644
index 0000000..ab436e4
--- /dev/null
+++ b/framework/logging/Filter.php
@@ -0,0 +1,107 @@
+
+ * @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
+ * @version $Id: CLogFilter.php 3204 2011-05-05 21:36:32Z alexander.makarow $
+ * @package system.logging
+ * @since 1.0.6
+ */
+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/Logger.php b/framework/logging/Logger.php
new file mode 100644
index 0000000..e09092b
--- /dev/null
+++ b/framework/logging/Logger.php
@@ -0,0 +1,320 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright © 2008-2012 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\logging;
+
+/**
+ * Logger records logged messages in memory.
+ *
+ * When [[flushInterval]] is reached or when application terminates, it will
+ * call [[flush]] to send logged messages to different log targets, such as
+ * file, email, Web.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Logger extends \yii\base\Component
+{
+ const LEVEL_TRACE = 'trace';
+ const LEVEL_WARN = 'warn';
+ const LEVEL_ERROR = 'error';
+ const LEVEL_INFO = 'info';
+ const LEVEL_PROFILE = 'profile';
+
+ /**
+ * @var integer how many messages should be logged before they are flushed from memory and sent to targets.
+ * Defaults to 1000, meaning the [[flush]] method will be invoked once every 1000 messages logged.
+ * Set this property to be 0 if you don't want to flush messages until the application terminates.
+ * This property mainly affects how much memory will be taken by the logged messages.
+ * A smaller value means less memory, but will increase the execution time due to the overhead of [[flush]].
+ */
+ public $flushInterval = 1000;
+ /**
+ * @var boolean this property will be passed as the parameter to [[flush]] when it is
+ * called due to the [[flushInterval]] is reached. Defaults to false, meaning the flushed
+ * messages are still kept in the memory by each log target. If this is true, they will
+ * be exported to the actual storage medium (e.g. DB, email) defined by each log target.
+ * @see flushInterval
+ */
+ public $autoExport = false;
+ /**
+ * @var array logged messages. This property is mainly managed by [[log]] and [[flush]].
+ */
+ public $messages = array();
+ /**
+ * @var array the profiling results (category, token => time in seconds)
+ */
+ private $_timings;
+
+ /**
+ * Logs an error message.
+ * An error message is typically logged when an unrecoverable error occurs
+ * during the execution of an application.
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
+ */
+ public function error($message, $category = 'application')
+ {
+ $this->log($message, self::LEVEL_ERROR, $category);
+ }
+
+ /**
+ * Logs a trace message.
+ * Trace messages are logged mainly for development purpose to see
+ * the execution work flow of some code.
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
+ */
+ public function trace($message, $category = 'application')
+ {
+ $this->log($message, self::LEVEL_TRACE, $category);
+ }
+
+ /**
+ * Logs a warning message.
+ * A warning message is typically logged when an error occurs while the execution
+ * can still continue.
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
+ */
+ public function warn($message, $category = 'application')
+ {
+ $this->log($message, self::LEVEL_TRACE, $category);
+ }
+
+ /**
+ * Logs an informative message.
+ * An informative message is typically logged by an application to keep record of
+ * something important (e.g. an administrator logs in).
+ * @param string $message the message to be logged.
+ * @param string $category the category of the message.
+ */
+ public function info($message, $category = 'application')
+ {
+ $this->log($message, self::LEVEL_TRACE, $category);
+ }
+
+ /**
+ * Marks the beginning of a code block for profiling.
+ * This has to be matched with a call to [[endProfile]] with the same category name.
+ * The begin- and end- calls must also be properly nested. For example,
+ * @param string $category the category of this profile block
+ * @see endProfile
+ */
+ public function beginProfile($category)
+ {
+ $this->log('begin', self::LEVEL_PROFILE, $category);
+ }
+
+ /**
+ * Marks the end of a code block for profiling.
+ * This has to be matched with a previous call to [[beginProfile]] with the same category name.
+ * @param string $category the category of this profile block
+ * @see beginProfile
+ */
+ public function endProfile($category)
+ {
+ $this->log('end', self::LEVEL_PROFILE, $category);
+ }
+
+ /**
+ * Logs a message with the given type and category.
+ * If `YII_DEBUG` is true and `YII_TRACE_LEVEL` is greater than 0, then additional
+ * call stack information about application code will be appended to the message.
+ * @param string $message the message to be logged.
+ * @param string $level the level of the message. This must be one of the following:
+ * 'trace', 'info', 'warn', 'error', 'profile'.
+ * @param string $category the category of the message.
+ */
+ public function log($message, $level, $category)
+ {
+ if (YII_DEBUG && YII_TRACE_LEVEL > 0 && $level !== self::LEVEL_PROFILE) {
+ $traces = debug_backtrace();
+ $count = 0;
+ foreach ($traces as $trace) {
+ if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII_DIR) !== 0) {
+ $message .= "\nin " . $trace['file'] . ' (' . $trace['line'] . ')';
+ if (++$count >= YII_TRACE_LEVEL) {
+ break;
+ }
+ }
+ }
+ }
+
+ $this->messages[] = array($message, $level, $category, microtime(true));
+ if (count($this->messages) >= $this->flushInterval && $this->flushInterval > 0) {
+ $this->flush($this->autoExport);
+ }
+ }
+
+ /**
+ * 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;
+ elseif (empty($levels))
+ return array_values(array_filter(array_filter($this->_logs, array($this, 'filterByCategory'))));
+ elseif (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 elapsed time since the start of the current request.
+ * This method calculates the difference between now and the timestamp
+ * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning
+ * of [[YiiBase]] class file.
+ * @return float the total elapsed time in seconds for current request.
+ */
+ public function getExecutionTime()
+ {
+ return microtime(true) - YII_BEGIN_TIME;
+ }
+
+ /**
+ * 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->messages as $log) {
+ if ($log[1] !== self::LEVEL_PROFILE) {
+ continue;
+ }
+ list($message, $level, $category, $timestamp) = $log;
+ if (!strncasecmp($message, 'begin:', 6)) {
+ $log[0] = substr($message, 6);
+ $stack[] = $log;
+ }
+ elseif (!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 \yii\base\Exception('Found a mismatching profiling block: ' . $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 $export whether to notify log targets to export the filtered messages they have received.
+ */
+ public function flush($export = false)
+ {
+ $this->onFlush(new \yii\base\Event($this, array('export' => $export)));
+ $this->messages = array();
+ }
+
+ /**
+ * Raises an `onFlush` event.
+ * @param \yii\base\Event $event the event parameter
+ */
+ public function onFlush($event)
+ {
+ $this->raiseEvent('onFlush', $event);
+ }
+}
diff --git a/framework/logging/ProfileTarget.php b/framework/logging/ProfileTarget.php
new file mode 100644
index 0000000..28100f9
--- /dev/null
+++ b/framework/logging/ProfileTarget.php
@@ -0,0 +1,201 @@
+
+ * @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:
+ *
+ * - summary: list the execution time of every marked code block
+ * - callstack: list the mark code blocks in a hierarchical view reflecting their calling sequence.
+ *
+ *
+ * @author Qiang Xue
+ * @version $Id: CProfileLogRoute.php 3204 2011-05-05 21:36:32Z alexander.makarow $
+ * @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'.
+ * @since 1.0.6
+ */
+ 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++;
+ }
+ elseif (!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;
+ }
+ elseif (!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;
+ elseif ($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/Router.php b/framework/logging/Router.php
new file mode 100644
index 0000000..b0ad8c2
--- /dev/null
+++ b/framework/logging/Router.php
@@ -0,0 +1,136 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright © 2008-2012 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\logging;
+
+/**
+ * Router manages [[Target|log targets]] that record log messages in different media.
+ *
+ * For example, a [[FileTarget|file log target]] records log messages
+ * in files; an [[EmailTarget|email log target]] sends log messages
+ * to specific email addresses. Each log target may specify filters on
+ * message levels and categories to record specific messages only.
+ *
+ * Router and the targets it manages may be configured in application configuration,
+ * like the following:
+ *
+ * ~~~
+ * array(
+ * // preload log component when application starts
+ * 'preload' => array('log'),
+ * 'components' => array(
+ * 'log' => array(
+ * 'class' => '\yii\logging\Router',
+ * 'targets' => array(
+ * 'file' => array(
+ * 'class' => '\yii\logging\FileTarget',
+ * 'levels' => 'trace, info',
+ * 'categories' => 'yii\*',
+ * ),
+ * 'email' => array(
+ * 'class' => '\yii\logging\EmailTarget',
+ * 'levels' => 'error, warning',
+ * 'emails' => array('admin@example.com'),
+ * ),
+ * ),
+ * ),
+ * ),
+ * )
+ * ~~~
+ *
+ * Each log target can have a name and can be referenced via the [[targets]] property
+ * as follows:
+ *
+ * ~~~
+ * \Yii::app()->log->targets['file']->enabled = false;
+ * ~~~
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Router extends \yii\base\ApplicationComponent
+{
+ private $_targets;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->_targets = new \yii\base\Dictionary;
+ }
+
+ /**
+ * Initializes this application component.
+ * This method is invoked when the Router component is created by the application.
+ * The method attaches the [[processLogs]] method to both the [[Logger::onFlush]] event
+ * and the [[\yii\base\Application::onEndRequest]] event.
+ */
+ public function init()
+ {
+ parent::init();
+ \Yii::getLogger()->attachEventHandler('onFlush', array($this, 'processMessages'));
+ if (($app = \Yii::app()) !== null) {
+ $app->attachEventHandler('onEndRequest', array($this, 'processMessages'));
+ }
+ }
+
+ /**
+ * Returns the log targets managed by this log router.
+ * The keys of the dictionary are the names of the log targets.
+ * You can use the name to access a specific log target. For example,
+ *
+ * ~~~
+ * $target = $router->targets['file'];
+ * ~~~
+ * @return \yii\base\Dictionary the targets managed by this log router.
+ */
+ public function getTargets()
+ {
+ return $this->_targets;
+ }
+
+ /**
+ * Sets the log targets.
+ * @param array $config list of log target configurations. Each array element
+ * represents the configuration for creating a single log target. It will be
+ * passed to [[\Yii::createComponent]] to create the target instance.
+ */
+ public function setTargets($config)
+ {
+ foreach ($config as $name => $target) {
+ if ($target instanceof Target) {
+ $this->_targets[$name] = $target;
+ }
+ else {
+ $this->_targets[$name] = \Yii::createComponent($target);
+ }
+ }
+ }
+
+ /**
+ * Retrieves and processes log messages from the system logger.
+ * This method mainly serves the event handler to [[Logger::onFlush]]
+ * and [[\yii\base\Application::onEndRequest]] events.
+ * It will retrieve the available log messages from the [[\Yii::getLogger|system logger]]
+ * and invoke the registered [[targets|log targets]] to do the actual processing.
+ * @param \yii\base\Event $event event parameter
+ */
+ public function processMessages($event)
+ {
+ $logger = Yii::getLogger();
+ $export = !isset($event->params['export']) || $event->params['export'];
+ foreach ($this->_targets as $target) {
+ if ($target->enabled) {
+ $target->processMessages($logger, $export);
+ }
+ }
+ }
+}
diff --git a/framework/logging/Target.php b/framework/logging/Target.php
new file mode 100644
index 0000000..b14cfbe
--- /dev/null
+++ b/framework/logging/Target.php
@@ -0,0 +1,171 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright © 2008-2012 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\logging;
+
+/**
+ * Target is the base class for all log target classes.
+ *
+ * A log target 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
+ * @since 2.0
+ */
+abstract class Target extends \yii\base\Component implements \yii\base\Initable
+{
+ /**
+ * @var boolean whether to enable this log target. 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 string list of categories that should be excluded.
+ */
+ public $excludeCategories;
+ /**
+ * @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 messages that are collected so far by this log target.
+ */
+ public $messages;
+
+ /**
+ * Pre-initializes this component.
+ * This method is required by the [[Initable]] interface. It is invoked by
+ * [[\Yii::createComponent]] after its creates the new component instance but
+ * BEFORE the component properties are initialized.
+ *
+ * You may override this method to do work such as setting property default values.
+ */
+ public function preinit()
+ {
+ }
+
+ /**
+ * Initializes this component.
+ * This method is invoked after the component is created and its property values are
+ * initialized.
+ */
+ 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 formatMessage($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 messages after they are collected from the logger
+ */
+ public function processMessages($logger, $export)
+ {
+ $messages = $logger->getLogs($this->levels, $this->categories);
+ $this->messages = empty($this->messages) ? $messages : array_merge($this->messages, $messages);
+ if ($processLogs && !empty($this->messages))
+ {
+ if ($this->filter !== null)
+ Yii::createComponent($this->filter)->filter($this->messages);
+ $this->processLogs($this->messages);
+ $this->messages = array();
+ }
+ }
+
+ protected function filterMessages($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;
+ elseif (empty($levels))
+ return array_values(array_filter(array_filter($this->_logs, array($this, 'filterByCategory'))));
+ elseif (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.
+ */
+ protected 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.
+ */
+ protected function filterByLevel($value)
+ {
+ return in_array(strtolower($value[1]), $this->_levels) ? $value : false;
+ }
+
+ /**
+ * Processes log messages and sends them to specific destination.
+ * Derived child classes must implement this method.
+ * @param array $messages 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($messages);
+}
diff --git a/framework/logging/WebTarget.php b/framework/logging/WebTarget.php
new file mode 100644
index 0000000..cdc622e
--- /dev/null
+++ b/framework/logging/WebTarget.php
@@ -0,0 +1,67 @@
+
+ * @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
+ * @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';
+ }
+ elseif (!($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