Qiang Xue
14 years ago
10 changed files with 1528 additions and 70 deletions
@ -0,0 +1,156 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* CDbLogRoute class file. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright © 2008-2011 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* CDbLogRoute stores log messages in a database table. |
||||||
|
* |
||||||
|
* To specify the database table for storing log messages, set {@link logTableName} as |
||||||
|
* the name of the table and specify {@link connectionID} to be the ID of a {@link CDbConnection} |
||||||
|
* application component. If they are not set, a SQLite3 database named 'log-YiiVersion.db' will be created |
||||||
|
* and used under the application runtime directory. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @version $Id: CDbLogRoute.php 3069 2011-03-14 00:28:38Z qiang.xue $ |
||||||
|
* @package system.logging |
||||||
|
* @since 1.0 |
||||||
|
*/ |
||||||
|
class CDbLogRoute extends CLogRoute |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @var string the ID of CDbConnection application component. If not set, a SQLite database |
||||||
|
* will be automatically created and used. The SQLite database file is |
||||||
|
* <code>protected/runtime/log-YiiVersion.db</code>. |
||||||
|
*/ |
||||||
|
public $connectionID; |
||||||
|
/** |
||||||
|
* @var string the name of the DB table that stores log content. Defaults to 'YiiLog'. |
||||||
|
* If {@link autoCreateLogTable} is false and you want to create the DB table manually by yourself, |
||||||
|
* you need to make sure the DB table is of the following structure: |
||||||
|
* <pre> |
||||||
|
* ( |
||||||
|
* id INTEGER NOT NULL PRIMARY KEY, |
||||||
|
* level VARCHAR(128), |
||||||
|
* category VARCHAR(128), |
||||||
|
* logtime INTEGER, |
||||||
|
* message TEXT |
||||||
|
* ) |
||||||
|
* </pre> |
||||||
|
* Note, the 'id' column must be created as an auto-incremental column. |
||||||
|
* In MySQL, this means it should be <code>id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY</code>; |
||||||
|
* In PostgreSQL, it is <code>id SERIAL PRIMARY KEY</code>. |
||||||
|
* @see autoCreateLogTable |
||||||
|
*/ |
||||||
|
public $logTableName = 'YiiLog'; |
||||||
|
/** |
||||||
|
* @var boolean whether the log DB table should be automatically created if not exists. Defaults to true. |
||||||
|
* @see logTableName |
||||||
|
*/ |
||||||
|
public $autoCreateLogTable = true; |
||||||
|
/** |
||||||
|
* @var CDbConnection the DB connection instance |
||||||
|
*/ |
||||||
|
private $_db; |
||||||
|
|
||||||
|
/** |
||||||
|
* Initializes the route. |
||||||
|
* This method is invoked after the route is created by the route manager. |
||||||
|
*/ |
||||||
|
public function init() |
||||||
|
{ |
||||||
|
parent::init(); |
||||||
|
|
||||||
|
if ($this->autoCreateLogTable) |
||||||
|
{ |
||||||
|
$db = $this->getDbConnection(); |
||||||
|
$sql = "DELETE FROM {$this->logTableName} WHERE 0=1"; |
||||||
|
try |
||||||
|
{ |
||||||
|
$db->createCommand($sql)->execute(); |
||||||
|
} |
||||||
|
catch(Exception $e) |
||||||
|
{ |
||||||
|
$this->createLogTable($db, $this->logTableName); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates the DB table for storing log messages. |
||||||
|
* @param CDbConnection $db the database connection |
||||||
|
* @param string $tableName the name of the table to be created |
||||||
|
*/ |
||||||
|
protected function createLogTable($db, $tableName) |
||||||
|
{ |
||||||
|
$driver = $db->getDriverName(); |
||||||
|
if ($driver === 'mysql') |
||||||
|
$logID = 'id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY'; |
||||||
|
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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,146 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* CEmailLogRoute class file. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright © 2008-2011 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* CEmailLogRoute sends selected log messages to email addresses. |
||||||
|
* |
||||||
|
* The target email addresses may be specified via {@link setEmails emails} property. |
||||||
|
* Optionally, you may set the email {@link setSubject subject}, the |
||||||
|
* {@link setSentFrom sentFrom} address and any additional {@link setHeaders headers}. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,167 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* CFileLogRoute class file. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright © 2008-2011 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* CFileLogRoute records log messages in files. |
||||||
|
* |
||||||
|
* The log files are stored under {@link setLogPath logPath} and the file name |
||||||
|
* is specified by {@link setLogFile logFile}. If the size of the log file is |
||||||
|
* greater than {@link setMaxFileSize maxFileSize} (in kilo-bytes), a rotation |
||||||
|
* is performed, which renames the current log file by suffixing the file name |
||||||
|
* with '.1'. All existing log files are moved backwards one place, i.e., '.2' |
||||||
|
* to '.3', '.1' to '.2'. The property {@link setMaxLogFiles maxLogFiles} |
||||||
|
* specifies how many files to be kept. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @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 |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,107 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* CLogFilter class file |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright © 2008-2011 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* CLogFilter preprocesses the logged messages before they are handled by a log route. |
||||||
|
* |
||||||
|
* CLogFilter is meant to be used by a log route to preprocess the logged messages |
||||||
|
* before they are handled by the route. The default implementation of CLogFilter |
||||||
|
* prepends additional context information to the logged messages. In particular, |
||||||
|
* by setting {@link logVars}, predefined PHP variables such as |
||||||
|
* $_SERVER, $_POST, etc. can be saved as a log message, which may help identify/debug |
||||||
|
* issues encountered. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @version $Id: CLogFilter.php 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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,320 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Logger class file |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @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 <qiang.xue@gmail.com> |
||||||
|
* @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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,201 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* CProfileLogRoute class file. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright © 2008-2011 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* CProfileLogRoute displays the profiling results in Web page. |
||||||
|
* |
||||||
|
* The profiling is done by calling {@link YiiBase::beginProfile()} and {@link YiiBase::endProfile()}, |
||||||
|
* which marks the begin and end of a code block. |
||||||
|
* |
||||||
|
* CProfileLogRoute supports two types of report by setting the {@link setReport report} property: |
||||||
|
* <ul> |
||||||
|
* <li>summary: list the execution time of every marked code block</li> |
||||||
|
* <li>callstack: list the mark code blocks in a hierarchical view reflecting their calling sequence.</li> |
||||||
|
* </ul> |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,136 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Router class file. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @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 <qiang.xue@gmail.com> |
||||||
|
* @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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,171 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Target class file. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @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 <qiang.xue@gmail.com> |
||||||
|
* @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); |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* CWebLogRoute class file. |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright © 2008-2011 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* CWebLogRoute shows the log content in Web page. |
||||||
|
* |
||||||
|
* The log content can appear either at the end of the current Web page |
||||||
|
* or in FireBug console window (if {@link showInFireBug} is set true). |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @version $Id: CWebLogRoute.php 3001 2011-02-24 16:42:44Z alexander.makarow $ |
||||||
|
* @package system.logging |
||||||
|
* @since 1.0 |
||||||
|
*/ |
||||||
|
class CWebLogRoute extends CLogRoute |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @var boolean whether the log should be displayed in FireBug instead of browser window. Defaults to false. |
||||||
|
*/ |
||||||
|
public $showInFireBug = false; |
||||||
|
|
||||||
|
/** |
||||||
|
* @var boolean whether the log should be ignored in FireBug for ajax calls. Defaults to true. |
||||||
|
* This option should be used carefully, because an ajax call returns all output as a result data. |
||||||
|
* For example if the ajax call expects a json type result any output from the logger will cause ajax call to fail. |
||||||
|
*/ |
||||||
|
public $ignoreAjaxInFireBug = true; |
||||||
|
|
||||||
|
/** |
||||||
|
* Displays the log messages. |
||||||
|
* @param array $logs list of log messages |
||||||
|
*/ |
||||||
|
public function processLogs($logs) |
||||||
|
{ |
||||||
|
$this->render('log', $logs); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Renders the view. |
||||||
|
* @param string $view the view name (file name without extension). The file is assumed to be located under framework/data/views. |
||||||
|
* @param array $data data to be passed to the view |
||||||
|
*/ |
||||||
|
protected function render($view, $data) |
||||||
|
{ |
||||||
|
$app = Yii::app(); |
||||||
|
$isAjax = $app->getRequest()->getIsAjaxRequest(); |
||||||
|
|
||||||
|
if ($this->showInFireBug) |
||||||
|
{ |
||||||
|
if ($isAjax && $this->ignoreAjaxInFireBug) |
||||||
|
return; |
||||||
|
$view .= '-firebug'; |
||||||
|
} |
||||||
|
elseif (!($app instanceof CWebApplication) || $isAjax) |
||||||
|
return; |
||||||
|
|
||||||
|
$viewFile = YII_PATH . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $view . '.php'; |
||||||
|
include($app->findLocalizedFile($viewFile, 'en')); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue