Browse Source

w

tags/2.0.0-beta
Qiang Xue 14 years ago
parent
commit
43777054f4
  1. 127
      framework/YiiBase.php
  2. 156
      framework/logging/DbTarget.php
  3. 146
      framework/logging/EmailTarget.php
  4. 167
      framework/logging/FileTarget.php
  5. 107
      framework/logging/Filter.php
  6. 320
      framework/logging/Logger.php
  7. 201
      framework/logging/ProfileTarget.php
  8. 136
      framework/logging/Router.php
  9. 171
      framework/logging/Target.php
  10. 67
      framework/logging/WebTarget.php

127
framework/YiiBase.php

@ -30,11 +30,10 @@ defined('YII_ENABLE_EXCEPTION_HANDLER') or define('YII_ENABLE_EXCEPTION_HANDLER'
*/ */
defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', true); defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', true);
/** /**
* Defines the Yii framework installation path. * This constant defines the framework installation directory.
*/ */
defined('YII_PATH') or define('YII_PATH', __DIR__); defined('YII_PATH') or define('YII_PATH', __DIR__);
/** /**
* YiiBase is the core helper class for the Yii framework. * YiiBase is the core helper class for the Yii framework.
* *
@ -66,7 +65,7 @@ class YiiBase
* @var array registered path aliases * @var array registered path aliases
*/ */
public static $aliases = array( public static $aliases = array(
'@yii' => YII_PATH, '@yii' => __DIR__,
); );
private static $_imported = array(); // alias => class name or directory private static $_imported = array(); // alias => class name or directory
@ -81,15 +80,6 @@ class YiiBase
} }
/** /**
* Returns the installation directory of the Yii framework.
* @return string the path of the framework
*/
public static function getFrameworkPath()
{
return YII_PATH;
}
/**
* Imports a class or a directory. * Imports a class or a directory.
* *
* Importing a class is like including the corresponding class file. * Importing a class is like including the corresponding class file.
@ -370,86 +360,83 @@ class YiiBase
} }
/** /**
* Writes a trace message. * Logs a trace message.
* This method will only log a message when the application is in debug mode. * Trace messages are logged mainly for development purpose to see
* @param string $msg message to be logged * the execution work flow of some code.
* @param string $category category of the message * @param string $message the message to be logged.
* @see log * @param string $category the category of the message.
*/ */
public static function trace($msg, $category = 'application') public static function trace($message, $category = 'application')
{ {
if (YII_DEBUG) { if (YII_DEBUG) {
static::log($msg, CLogger::LEVEL_TRACE, $category); self::getLogger()->trace($message, $category);
} }
} }
/** /**
* Logs a message. * Logs an error message.
* Messages logged by this method may be retrieved via {@link CLogger::getLogs} * An error message is typically logged when an unrecoverable error occurs
* and may be recorded in different media, such as file, email, database, using * during the execution of an application.
* {@link CLogRouter}. * @param string $message the message to be logged.
* @param string $msg message to be logged * @param string $category the category of the message.
* @param string $level level of the message (e.g. 'trace', 'warning', 'error'). It is case-insensitive.
* @param string $category category of the message (e.g. 'system.web'). It is case-insensitive.
*/ */
public static function log($msg, $level = CLogger::LEVEL_INFO, $category = 'application') public function error($message, $category = 'application')
{ {
if (self::$_logger === null) { self::getLogger()->error($message, $category);
self::$_logger = new CLogger;
}
if (YII_DEBUG && YII_TRACE_LEVEL > 0 && $level !== CLogger::LEVEL_PROFILE)
{
$traces = debug_backtrace();
$count = 0;
foreach ($traces as $trace)
{
if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII_PATH) !== 0)
{
$msg .= "\nin " . $trace['file'] . ' (' . $trace['line'] . ')';
if (++$count >= YII_TRACE_LEVEL)
break;
}
}
}
self::$_logger->log($msg, $level, $category);
} }
/** /**
* Marks the begin of a code block for profiling. * Logs a warning message.
* This has to be matched with a call to {@link endProfile()} with the same token. * A warning message is typically logged when an error occurs while the execution
* The begin- and end- calls must also be properly nested, e.g., * can still continue.
* <pre> * @param string $message the message to be logged.
* Yii::beginProfile('block1'); * @param string $category the category of the message.
* Yii::beginProfile('block2'); */
* Yii::endProfile('block2'); public function warn($message, $category = 'application')
* Yii::endProfile('block1'); {
* </pre> self::getLogger()->warn($message, $category);
* The following sequence is not valid: }
* <pre>
* Yii::beginProfile('block1'); /**
* Yii::beginProfile('block2'); * Logs an informative message.
* Yii::endProfile('block1'); * An informative message is typically logged by an application to keep record of
* Yii::endProfile('block2'); * something important (e.g. an administrator logs in).
* </pre> * @param string $message the message to be logged.
* @param string $token token for the code block * @param string $category the category of the message.
* @param string $category the category of this log message */
public function info($message, $category = 'application')
{
self::getLogger()->info($message, $category);
}
/**
* Marks the beginning of a code block for profiling.
* This has to be matched with a call to [[endProfile]] with the same category name.
* The begin- and end- calls must also be properly nested. For example,
*
* ~~~
* \Yii::beginProfile('block1');
* \Yii::beginProfile('block2');
* \Yii::endProfile('block2');
* \Yii::endProfile('block1');
* ~~~
* @param string $category the category of this profile block
* @see endProfile * @see endProfile
*/ */
public static function beginProfile($token, $category = 'application') public static function beginProfile($category)
{ {
static::log('begin:' . $token, CLogger::LEVEL_PROFILE, $category); self::getLogger()->beginProfile($category);
} }
/** /**
* Marks the end of a code block for profiling. * Marks the end of a code block for profiling.
* This has to be matched with a previous call to {@link beginProfile()} with the same token. * This has to be matched with a previous call to [[beginProfile]] with the same category name.
* @param string $token token for the code block * @param string $category the category of this profile block
* @param string $category the category of this log message
* @see beginProfile * @see beginProfile
*/ */
public static function endProfile($token, $category = 'application') public static function endProfile($category)
{ {
static::log('end:' . $token, CLogger::LEVEL_PROFILE, $category); self::getLogger()->endProfile($category);
} }
/** /**

156
framework/logging/DbTarget.php

@ -0,0 +1,156 @@
<?php
/**
* CDbLogRoute class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDbLogRoute stores log messages in a database table.
*
* To specify the database table for storing log messages, set {@link logTableName} as
* the name of the table and specify {@link connectionID} to be the ID of a {@link CDbConnection}
* application component. If they are not set, a SQLite3 database named 'log-YiiVersion.db' will be created
* and used under the application runtime directory.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CDbLogRoute.php 3069 2011-03-14 00:28:38Z qiang.xue $
* @package system.logging
* @since 1.0
*/
class CDbLogRoute extends CLogRoute
{
/**
* @var string the ID of CDbConnection application component. If not set, a SQLite database
* will be automatically created and used. The SQLite database file is
* <code>protected/runtime/log-YiiVersion.db</code>.
*/
public $connectionID;
/**
* @var string the name of the DB table that stores log content. Defaults to 'YiiLog'.
* If {@link autoCreateLogTable} is false and you want to create the DB table manually by yourself,
* you need to make sure the DB table is of the following structure:
* <pre>
* (
* id INTEGER NOT NULL PRIMARY KEY,
* level VARCHAR(128),
* category VARCHAR(128),
* logtime INTEGER,
* message TEXT
* )
* </pre>
* Note, the 'id' column must be created as an auto-incremental column.
* In MySQL, this means it should be <code>id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY</code>;
* In PostgreSQL, it is <code>id SERIAL PRIMARY KEY</code>.
* @see autoCreateLogTable
*/
public $logTableName = 'YiiLog';
/**
* @var boolean whether the log DB table should be automatically created if not exists. Defaults to true.
* @see logTableName
*/
public $autoCreateLogTable = true;
/**
* @var CDbConnection the DB connection instance
*/
private $_db;
/**
* Initializes the route.
* This method is invoked after the route is created by the route manager.
*/
public function init()
{
parent::init();
if ($this->autoCreateLogTable)
{
$db = $this->getDbConnection();
$sql = "DELETE FROM {$this->logTableName} WHERE 0=1";
try
{
$db->createCommand($sql)->execute();
}
catch(Exception $e)
{
$this->createLogTable($db, $this->logTableName);
}
}
}
/**
* Creates the DB table for storing log messages.
* @param CDbConnection $db the database connection
* @param string $tableName the name of the table to be created
*/
protected function createLogTable($db, $tableName)
{
$driver = $db->getDriverName();
if ($driver === 'mysql')
$logID = 'id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY';
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();
}
}
}

146
framework/logging/EmailTarget.php

@ -0,0 +1,146 @@
<?php
/**
* CEmailLogRoute class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CEmailLogRoute sends selected log messages to email addresses.
*
* The target email addresses may be specified via {@link setEmails emails} property.
* Optionally, you may set the email {@link setSubject subject}, the
* {@link setSentFrom sentFrom} address and any additional {@link setHeaders headers}.
*
* @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);
}
}

167
framework/logging/FileTarget.php

@ -0,0 +1,167 @@
<?php
/**
* CFileLogRoute class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CFileLogRoute records log messages in files.
*
* The log files are stored under {@link setLogPath logPath} and the file name
* is specified by {@link setLogFile logFile}. If the size of the log file is
* greater than {@link setMaxFileSize maxFileSize} (in kilo-bytes), a rotation
* is performed, which renames the current log file by suffixing the file name
* with '.1'. All existing log files are moved backwards one place, i.e., '.2'
* to '.3', '.1' to '.2'. The property {@link setMaxLogFiles maxLogFiles}
* specifies how many files to be kept.
*
* @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
}
}

107
framework/logging/Filter.php

@ -0,0 +1,107 @@
<?php
/**
* CLogFilter class file
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CLogFilter preprocesses the logged messages before they are handled by a log route.
*
* CLogFilter is meant to be used by a log route to preprocess the logged messages
* before they are handled by the route. The default implementation of CLogFilter
* prepends additional context information to the logged messages. In particular,
* by setting {@link logVars}, predefined PHP variables such as
* $_SERVER, $_POST, etc. can be saved as a log message, which may help identify/debug
* issues encountered.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CLogFilter.php 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);
}
}

320
framework/logging/Logger.php

@ -0,0 +1,320 @@
<?php
/**
* Logger class file
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 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);
}
}

201
framework/logging/ProfileTarget.php

@ -0,0 +1,201 @@
<?php
/**
* CProfileLogRoute class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CProfileLogRoute displays the profiling results in Web page.
*
* The profiling is done by calling {@link YiiBase::beginProfile()} and {@link YiiBase::endProfile()},
* which marks the begin and end of a code block.
*
* CProfileLogRoute supports two types of report by setting the {@link setReport report} property:
* <ul>
* <li>summary: list the execution time of every marked code block</li>
* <li>callstack: list the mark code blocks in a hierarchical view reflecting their calling sequence.</li>
* </ul>
*
* @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);
}
}

136
framework/logging/Router.php

@ -0,0 +1,136 @@
<?php
/**
* Router class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 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);
}
}
}
}

171
framework/logging/Target.php

@ -0,0 +1,171 @@
<?php
/**
* Target class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 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);
}

67
framework/logging/WebTarget.php

@ -0,0 +1,67 @@
<?php
/**
* CWebLogRoute class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CWebLogRoute shows the log content in Web page.
*
* The log content can appear either at the end of the current Web page
* or in FireBug console window (if {@link showInFireBug} is set true).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CWebLogRoute.php 3001 2011-02-24 16:42:44Z alexander.makarow $
* @package system.logging
* @since 1.0
*/
class CWebLogRoute extends CLogRoute
{
/**
* @var boolean whether the log should be displayed in FireBug instead of browser window. Defaults to false.
*/
public $showInFireBug = false;
/**
* @var boolean whether the log should be ignored in FireBug for ajax calls. Defaults to true.
* This option should be used carefully, because an ajax call returns all output as a result data.
* For example if the ajax call expects a json type result any output from the logger will cause ajax call to fail.
*/
public $ignoreAjaxInFireBug = true;
/**
* Displays the log messages.
* @param array $logs list of log messages
*/
public function processLogs($logs)
{
$this->render('log', $logs);
}
/**
* Renders the view.
* @param string $view the view name (file name without extension). The file is assumed to be located under framework/data/views.
* @param array $data data to be passed to the view
*/
protected function render($view, $data)
{
$app = Yii::app();
$isAjax = $app->getRequest()->getIsAjaxRequest();
if ($this->showInFireBug)
{
if ($isAjax && $this->ignoreAjaxInFireBug)
return;
$view .= '-firebug';
}
elseif (!($app instanceof CWebApplication) || $isAjax)
return;
$viewFile = YII_PATH . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $view . '.php';
include($app->findLocalizedFile($viewFile, 'en'));
}
}
Loading…
Cancel
Save