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