Commit fb75c958 by Qiang Xue

refactored logging.

parent 373e6a70
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
*/ */
use yii\base\Exception; use yii\base\Exception;
use yii\logging\Logger;
use yii\base\InvalidCallException; use yii\base\InvalidCallException;
use yii\base\InvalidConfigException;
/** /**
* Gets the application start timestamp. * Gets the application start timestamp.
...@@ -394,7 +396,7 @@ class YiiBase ...@@ -394,7 +396,7 @@ class YiiBase
public static function trace($message, $category = 'application') public static function trace($message, $category = 'application')
{ {
if (YII_DEBUG) { if (YII_DEBUG) {
self::getLogger()->trace($message, $category); self::getLogger()->log($message, Logger::LEVEL_TRACE, $category);
} }
} }
...@@ -407,7 +409,7 @@ class YiiBase ...@@ -407,7 +409,7 @@ class YiiBase
*/ */
public static function error($message, $category = 'application') public static function error($message, $category = 'application')
{ {
self::getLogger()->error($message, $category); self::getLogger()->log($message, Logger::LEVEL_ERROR, $category);
} }
/** /**
...@@ -419,7 +421,7 @@ class YiiBase ...@@ -419,7 +421,7 @@ class YiiBase
*/ */
public static function warning($message, $category = 'application') public static function warning($message, $category = 'application')
{ {
self::getLogger()->warning($message, $category); self::getLogger()->log($message, Logger::LEVEL_WARNING, $category);
} }
/** /**
...@@ -431,7 +433,7 @@ class YiiBase ...@@ -431,7 +433,7 @@ class YiiBase
*/ */
public static function info($message, $category = 'application') public static function info($message, $category = 'application')
{ {
self::getLogger()->info($message, $category); self::getLogger()->log($message, Logger::LEVEL_INFO, $category);
} }
/** /**
...@@ -453,7 +455,7 @@ class YiiBase ...@@ -453,7 +455,7 @@ class YiiBase
*/ */
public static function beginProfile($token, $category = 'application') public static function beginProfile($token, $category = 'application')
{ {
self::getLogger()->beginProfile($token, $category); self::getLogger()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category);
} }
/** /**
...@@ -465,7 +467,7 @@ class YiiBase ...@@ -465,7 +467,7 @@ class YiiBase
*/ */
public static function endProfile($token, $category = 'application') public static function endProfile($token, $category = 'application')
{ {
self::getLogger()->endProfile($token, $category); self::getLogger()->log($token, Logger::LEVEL_PROFILE_END, $category);
} }
/** /**
...@@ -477,13 +479,13 @@ class YiiBase ...@@ -477,13 +479,13 @@ class YiiBase
if (self::$_logger !== null) { if (self::$_logger !== null) {
return self::$_logger; return self::$_logger;
} else { } else {
return self::$_logger = new \yii\logging\Logger; return self::$_logger = new Logger;
} }
} }
/** /**
* Sets the logger object. * Sets the logger object.
* @param \yii\logging\Logger $logger the logger object. * @param Logger $logger the logger object.
*/ */
public static function setLogger($logger) public static function setLogger($logger)
{ {
......
...@@ -75,6 +75,8 @@ use yii\base\InvalidCallException; ...@@ -75,6 +75,8 @@ use yii\base\InvalidCallException;
*/ */
class Application extends Module class Application extends Module
{ {
const EVENT_BEFORE_REQUEST = 'beforeRequest';
const EVENT_AFTER_REQUEST = 'afterRequest';
/** /**
* @var string the application name. Defaults to 'My Application'. * @var string the application name. Defaults to 'My Application'.
*/ */
...@@ -178,7 +180,7 @@ class Application extends Module ...@@ -178,7 +180,7 @@ class Application extends Module
*/ */
public function beforeRequest() public function beforeRequest()
{ {
$this->trigger('beforeRequest'); $this->trigger(self::EVENT_BEFORE_REQUEST);
} }
/** /**
...@@ -186,7 +188,7 @@ class Application extends Module ...@@ -186,7 +188,7 @@ class Application extends Module
*/ */
public function afterRequest() public function afterRequest()
{ {
$this->trigger('afterRequest'); $this->trigger(self::EVENT_AFTER_REQUEST);
} }
/** /**
......
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
namespace yii\logging; namespace yii\logging;
use yii\db\Connection;
use yii\base\InvalidConfigException;
/** /**
* DbTarget stores log messages in a database table. * DbTarget stores log messages in a database table.
* *
...@@ -23,23 +26,21 @@ namespace yii\logging; ...@@ -23,23 +26,21 @@ namespace yii\logging;
class DbTarget extends Target class DbTarget extends Target
{ {
/** /**
* @var string the ID of [[\yii\db\Connection]] application component. * @var string the ID of [[Connection]] application component.
* Defaults to 'db'. Please make sure that your database contains a table * Defaults to 'db'. Please make sure that your database contains a table
* whose name is as specified in [[tableName]] and has the required table structure. * whose name is as specified in [[tableName]] and has the required table structure.
* @see tableName * @see tableName
*/ */
public $connectionID = 'db'; public $connectionID = 'db';
/** /**
* @var string the name of the DB table that stores log messages. Defaults to '{{log}}'. * @var string the name of the DB table that stores log messages. Defaults to 'tbl_log'.
* If you are using table prefix 'tbl_' (configured via [[\yii\db\Connection::tablePrefix]]),
* it means the DB table would be named as 'tbl_log'.
* *
* The DB table must have the following structure: * The DB table should have the following structure:
* *
* ~~~ * ~~~
* CREATE TABLE tbl_log ( * CREATE TABLE tbl_log (
* id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, * id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
* level VARCHAR(32), * level INTEGER,
* category VARCHAR(255), * category VARCHAR(255),
* log_time INTEGER, * log_time INTEGER,
* message TEXT, * message TEXT,
...@@ -50,42 +51,53 @@ class DbTarget extends Target ...@@ -50,42 +51,53 @@ class DbTarget extends Target
* *
* Note that the 'id' column must be created as an auto-incremental column. * Note that the 'id' column must be created as an auto-incremental column.
* The above SQL shows the syntax of MySQL. If you are using other DBMS, you need * The above SQL shows the syntax of MySQL. If you are using other DBMS, you need
* to adjust it accordingly. For example, in PosgreSQL, it should be `id SERIAL PRIMARY KEY`. * to adjust it accordingly. For example, in PostgreSQL, it should be `id SERIAL PRIMARY KEY`.
* *
* The indexes declared above are not required. They are mainly used to improve the performance * The indexes declared above are not required. They are mainly used to improve the performance
* of some queries about message levels and categories. Depending on your actual needs, you may * of some queries about message levels and categories. Depending on your actual needs, you may
* want to create other indexes. * want to create additional indexes (e.g. index on log_time).
*/ */
public $tableName = '{{log}}'; public $tableName = 'tbl_log';
private $_db; private $_db;
/** /**
* Returns the DB connection used for saving log messages. * Returns the DB connection used for saving log messages.
* @return \yii\db\Connection the DB connection instance * @return Connection the DB connection instance
* @throws \yii\base\Exception if [[connectionID]] does not refer to a valid application component ID. * @throws InvalidConfigException if [[connectionID]] does not point to a valid application component.
*/ */
public function getDb() public function getDb()
{ {
if ($this->_db === null) { if ($this->_db === null) {
$this->_db = \Yii::$application->getComponent($this->connectionID); $db = \Yii::$application->getComponent($this->connectionID);
if (!$this->_db instanceof \yii\db\Connection) { if ($db instanceof Connection) {
throw new \yii\base\Exception('DbTarget.connectionID must refer to a valid application component ID'); $this->_db = $db;
} else {
throw new InvalidConfigException("DbTarget::connectionID must refer to the ID of a DB application component.");
} }
} }
return $this->_db; return $this->_db;
} }
/** /**
* Sets the DB connection used by the cache component.
* @param Connection $value the DB connection instance
*/
public function setDb($value)
{
$this->_db = $value;
}
/**
* Stores log [[messages]] to DB. * Stores log [[messages]] to DB.
* @param boolean $final whether this method is called at the end of the current application * @param boolean $final whether this method is called at the end of the current application
*/ */
public function exportMessages($final) public function exportMessages($final)
{ {
$sql = "INSERT INTO {$this->tableName} $db = $this->getDb();
(level, category, log_time, message) VALUES $tableName = $db->quoteTableName($this->tableName);
(:level, :category, :log_time, :message)"; $sql = "INSERT INTO $tableName (level, category, log_time, message) VALUES (:level, :category, :log_time, :message)";
$command = $this->getDb()->createCommand($sql); $command = $db->createCommand($sql);
foreach ($this->messages as $message) { foreach ($this->messages as $message) {
$command->bindValues(array( $command->bindValues(array(
':level' => $message[1], ':level' => $message[1],
......
...@@ -8,17 +8,16 @@ ...@@ -8,17 +8,16 @@
*/ */
namespace yii\logging; namespace yii\logging;
use yii\base\InvalidConfigException;
/** /**
* FileTarget records log messages in files. * FileTarget records log messages in a file.
* *
* The log files are stored under [[logPath]] and their name * The log file is specified via [[logFile]]. If the size of the log file exceeds
* is specified by [[logFile]]. If the size of the log file exceeds * [[maxFileSize]] (in kilo-bytes), a rotation will be performed, which renames
* [[maxFileSize]] (in kilo-bytes), a rotation will be performed, * the current log file by suffixing the file name with '.1'. All existing log
* which renames the current log file by suffixing the file name * files are moved backwards by one place, i.e., '.2' to '.3', '.1' to '.2', and so on.
* with '.1'. All existing log files are moved backwards one place, * The property [[maxLogFiles]] specifies how many files to keep.
* i.e., '.2' to '.3', '.1' to '.2', and so on. The property
* [[maxLogFiles]] specifies how many files to keep.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
...@@ -26,6 +25,12 @@ namespace yii\logging; ...@@ -26,6 +25,12 @@ namespace yii\logging;
class FileTarget extends Target class FileTarget extends Target
{ {
/** /**
* @var string log file path or path alias. If not set, it means the 'application.log' file under
* the application runtime directory. Please make sure the directory containing
* the log file is writable by the Web server process.
*/
public $logFile;
/**
* @var integer maximum log file size, in kilo-bytes. Defaults to 1024, meaning 1MB. * @var integer maximum log file size, in kilo-bytes. Defaults to 1024, meaning 1MB.
*/ */
public $maxFileSize = 1024; // in KB public $maxFileSize = 1024; // in KB
...@@ -33,14 +38,6 @@ class FileTarget extends Target ...@@ -33,14 +38,6 @@ class FileTarget extends Target
* @var integer number of log files used for rotation. Defaults to 5. * @var integer number of log files used for rotation. Defaults to 5.
*/ */
public $maxLogFiles = 5; public $maxLogFiles = 5;
/**
* @var string directory storing log files. Defaults to the application runtime path.
*/
public $logPath;
/**
* @var string log file name. Defaults to 'application.log'.
*/
public $logFile = 'application.log';
/** /**
...@@ -50,11 +47,14 @@ class FileTarget extends Target ...@@ -50,11 +47,14 @@ class FileTarget extends Target
public function init() public function init()
{ {
parent::init(); parent::init();
if ($this->logPath === null) { if ($this->logFile === null) {
$this->logPath = \Yii::$application->getRuntimePath(); $this->logFile = \Yii::$application->getRuntimePath() . DIRECTORY_SEPARATOR . 'application.log';
} else {
$this->logFile = \Yii::getAlias($this->logFile);
} }
if (!is_dir($this->logPath) || !is_writable($this->logPath)) { $logPath = dirname($this->logFile);
throw new \yii\base\Exception("Directory '{$this->logPath}' does not exist or is not writable."); if (!is_dir($logPath) || !is_writable($logPath)) {
throw new InvalidConfigException("Directory '$logPath' does not exist or is not writable.");
} }
if ($this->maxLogFiles < 1) { if ($this->maxLogFiles < 1) {
$this->maxLogFiles = 1; $this->maxLogFiles = 1;
...@@ -70,15 +70,14 @@ class FileTarget extends Target ...@@ -70,15 +70,14 @@ class FileTarget extends Target
*/ */
public function exportMessages($final) public function exportMessages($final)
{ {
$logFile = $this->logPath . DIRECTORY_SEPARATOR . $this->logFile; if (@filesize($this->logFile) > $this->maxFileSize * 1024) {
if (@filesize($logFile) > $this->maxFileSize * 1024) {
$this->rotateFiles(); $this->rotateFiles();
} }
$messages = array(); $messages = array();
foreach ($this->messages as $message) { foreach ($this->messages as $message) {
$messages[] = $this->formatMessage($message); $messages[] = $this->formatMessage($message);
} }
@file_put_contents($logFile, implode('', $messages), FILE_APPEND | LOCK_EX); @file_put_contents($this->logFile, implode('', $messages), FILE_APPEND | LOCK_EX);
} }
/** /**
...@@ -86,7 +85,7 @@ class FileTarget extends Target ...@@ -86,7 +85,7 @@ class FileTarget extends Target
*/ */
protected function rotateFiles() protected function rotateFiles()
{ {
$file = $this->logPath . DIRECTORY_SEPARATOR . $this->logFile; $file = $this->logFile;
for ($i = $this->maxLogFiles; $i > 0; --$i) { for ($i = $this->maxLogFiles; $i > 0; --$i) {
$rotateFile = $file . '.' . $i; $rotateFile = $file . '.' . $i;
if (is_file($rotateFile)) { if (is_file($rotateFile)) {
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
namespace yii\logging; namespace yii\logging;
use yii\base\Event; use yii\base\Event;
use yii\base\Exception;
/** /**
* Logger records logged messages in memory. * Logger records logged messages in memory.
...@@ -18,8 +19,6 @@ use yii\base\Event; ...@@ -18,8 +19,6 @@ use yii\base\Event;
* call [[flush()]] to send logged messages to different log targets, such as * call [[flush()]] to send logged messages to different log targets, such as
* file, email, Web. * file, email, Web.
* *
* Logger provides a set of events for further customization:
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
...@@ -30,12 +29,40 @@ class Logger extends \yii\base\Component ...@@ -30,12 +29,40 @@ class Logger extends \yii\base\Component
*/ */
const EVENT_FLUSH = 'flush'; const EVENT_FLUSH = 'flush';
const LEVEL_ERROR = 'error'; /**
const LEVEL_WARNING = 'warning'; * Error message level. An error message is one that indicates the abnormal termination of the
const LEVEL_INFO = 'info'; * application and may require developer's handling.
const LEVEL_TRACE = 'trace'; */
const LEVEL_PROFILE_BEGIN = 'profile-begin'; const LEVEL_ERROR = 0x01;
const LEVEL_PROFILE_END = 'profile-end'; /**
* Warning message level. A warning message is one that indicates some abnormal happens but
* the application is able to continue to run. Developers should pay attention to this message.
*/
const LEVEL_WARNING = 0x02;
/**
* Informational message level. An informational message is one that includes certain information
* for developers to review.
*/
const LEVEL_INFO = 0x04;
/**
* Tracing message level. An tracing message is one that reveals the code execution flow.
*/
const LEVEL_TRACE = 0x08;
/**
* Profiling message level. This indicates the message is for profiling purpose.
*/
const LEVEL_PROFILE = 0x40;
/**
* Profiling message level. This indicates the message is for profiling purpose. It marks the
* beginning of a profiling block.
*/
const LEVEL_PROFILE_BEGIN = 0x50;
/**
* Profiling message level. This indicates the message is for profiling purpose. It marks the
* end of a profiling block.
*/
const LEVEL_PROFILE_END = 0x60;
/** /**
* @var integer how many messages should be logged before they are flushed from memory and sent to targets. * @var integer how many messages should be logged before they are flushed from memory and sent to targets.
...@@ -52,7 +79,7 @@ class Logger extends \yii\base\Component ...@@ -52,7 +79,7 @@ class Logger extends \yii\base\Component
* ~~~ * ~~~
* array( * array(
* [0] => message (mixed) * [0] => message (mixed)
* [1] => level (string) * [1] => level (integer)
* [2] => category (string) * [2] => category (string)
* [3] => timestamp (float, obtained by microtime(true)) * [3] => timestamp (float, obtained by microtime(true))
* ) * )
...@@ -61,85 +88,13 @@ class Logger extends \yii\base\Component ...@@ -61,85 +88,13 @@ class Logger extends \yii\base\Component
public $messages = array(); public $messages = array();
/** /**
* Logs an error message.
* An error message is typically logged when an unrecoverable error occurs
* during the execution of an application.
* @param mixed $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 mixed $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 mixed $message the message to be logged.
* @param string $category the category of the message.
*/
public function warning($message, $category = 'application')
{
$this->log($message, self::LEVEL_WARNING, $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 mixed $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_INFO, $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.
* @param string $token token for the code block
* @param string $category the category of this log message
* @see endProfile
*/
public function beginProfile($token, $category = 'application')
{
$this->log($token, self::LEVEL_PROFILE_BEGIN, $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 $token token for the code block
* @param string $category the category of this log message
* @see beginProfile
*/
public function endProfile($token, $category = 'application')
{
$this->log($token, self::LEVEL_PROFILE_END, $category);
}
/**
* Logs a message with the given type and 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 * 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. * call stack information about application code will be appended to the message.
* @param string $message the message to be logged. * @param string $message the message to be logged.
* @param string $level the level of the message. This must be one of the following: * @param integer $level the level of the message. This must be one of the following:
* 'trace', 'info', 'warning', 'error', 'profile'. * `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`,
* `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`.
* @param string $category the category of the message. * @param string $category the category of the message.
*/ */
public function log($message, $level, $category = 'application') public function log($message, $level, $category = 'application')
...@@ -242,14 +197,14 @@ class Logger extends \yii\base\Component ...@@ -242,14 +197,14 @@ class Logger extends \yii\base\Component
$stack = array(); $stack = array();
foreach ($this->messages as $log) { foreach ($this->messages as $log) {
if ($log[1] === self::LEVEL_PROFILE_BEGIN) { list($token, $level, $category, $timestamp) = $log;
if ($level == self::LEVEL_PROFILE_BEGIN) {
$stack[] = $log; $stack[] = $log;
} elseif ($log[1] === self::LEVEL_PROFILE_END) { } elseif ($level == self::LEVEL_PROFILE_END) {
list($token, $level, $category, $timestamp) = $log;
if (($last = array_pop($stack)) !== null && $last[0] === $token) { if (($last = array_pop($stack)) !== null && $last[0] === $token) {
$timings[] = array($token, $category, $timestamp - $last[3]); $timings[] = array($token, $category, $timestamp - $last[3]);
} else { } else {
throw new \yii\base\Exception("Unmatched profiling block: $token"); throw new Exception("Unmatched profiling block: $token");
} }
} }
} }
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
namespace yii\logging; namespace yii\logging;
use Yii;
use yii\base\Component;
use yii\base\Application;
/** /**
* Router manages [[Target|log targets]] that record log messages in different media. * Router manages [[Target|log targets]] that record log messages in different media.
* *
...@@ -48,17 +52,17 @@ namespace yii\logging; ...@@ -48,17 +52,17 @@ namespace yii\logging;
* as follows: * as follows:
* *
* ~~~ * ~~~
* \Yii::$application->log->targets['file']->enabled = false; * Yii::$application->log->targets['file']->enabled = false;
* ~~~ * ~~~
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Router extends \yii\base\ApplicationComponent class Router extends Component
{ {
/** /**
* @var Target[] list of log target objects or configurations. If the latter, target objects will * @var Target[] list of log target objects or configurations. If the latter, target objects will
* be created in [[init()]] by calling [[\Yii::createObject()]] with the corresponding object configuration. * be created in [[init()]] by calling [[Yii::createObject()]] with the corresponding object configuration.
*/ */
public $targets = array(); public $targets = array();
...@@ -74,28 +78,28 @@ class Router extends \yii\base\ApplicationComponent ...@@ -74,28 +78,28 @@ class Router extends \yii\base\ApplicationComponent
foreach ($this->targets as $name => $target) { foreach ($this->targets as $name => $target) {
if (!$target instanceof Target) { if (!$target instanceof Target) {
$this->targets[$name] = \Yii::createObject($target); $this->targets[$name] = Yii::createObject($target);
} }
} }
\Yii::getLogger()->on('flush', array($this, 'processMessages')); Yii::getLogger()->on(Logger::EVENT_FLUSH, array($this, 'processMessages'));
if (\Yii::$application !== null) { if (Yii::$application !== null) {
\Yii::$application->on('afterRequest', array($this, 'processMessages')); Yii::$application->on(Application::EVENT_AFTER_REQUEST, array($this, 'processMessages'));
} }
} }
/** /**
* Retrieves and processes log messages from the system logger. * Retrieves and processes log messages from the system logger.
* This method mainly serves the event handler to [[Logger::onFlush]] * This method mainly serves the event handler to [[Logger::onFlush]]
* and [[\yii\base\Application::onEndRequest]] events. * and [[Application::onEndRequest]] events.
* It will retrieve the available log messages from the [[\Yii::getLogger|system logger]] * 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. * and invoke the registered [[targets|log targets]] to do the actual processing.
* @param \yii\base\Event $event event parameter * @param \yii\base\Event $event event parameter
*/ */
public function processMessages($event) public function processMessages($event)
{ {
$messages = \Yii::getLogger()->messages; $messages = Yii::getLogger()->messages;
$final = $event->name !== 'flush'; $final = $event->name === Application::EVENT_AFTER_REQUEST;
foreach ($this->targets as $target) { foreach ($this->targets as $target) {
if ($target->enabled) { if ($target->enabled) {
$target->processMessages($messages, $final); $target->processMessages($messages, $final);
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
namespace yii\logging; namespace yii\logging;
use yii\base\InvalidConfigException;
/** /**
* Target is the base class for all log target classes. * Target is the base class for all log target classes.
* *
...@@ -20,6 +22,8 @@ namespace yii\logging; ...@@ -20,6 +22,8 @@ namespace yii\logging;
* satisfying both filter conditions will be handled. Additionally, you * satisfying both filter conditions will be handled. Additionally, you
* may specify [[except]] to exclude messages of certain categories. * may specify [[except]] to exclude messages of certain categories.
* *
* @property integer $levels the message levels that this target is interested in.
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
...@@ -30,10 +34,6 @@ abstract class Target extends \yii\base\Component ...@@ -30,10 +34,6 @@ abstract class Target extends \yii\base\Component
*/ */
public $enabled = true; public $enabled = true;
/** /**
* @var array list of message levels that this target is interested in. Defaults to empty, meaning all levels.
*/
public $levels = array();
/**
* @var array list of message categories that this target is interested in. Defaults to empty, meaning all categories. * @var array list of message categories that this target is interested in. Defaults to empty, meaning all categories.
* You can use an asterisk at the end of a category so that the category may be used to * You can use an asterisk at the end of a category so that the category may be used to
* match those categories sharing the same common prefix. For example, 'yii\db\*' will match * match those categories sharing the same common prefix. For example, 'yii\db\*' will match
...@@ -70,17 +70,19 @@ abstract class Target extends \yii\base\Component ...@@ -70,17 +70,19 @@ abstract class Target extends \yii\base\Component
*/ */
public $logVars = array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'); public $logVars = array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER');
/** /**
* @var boolean whether this target should export the collected messages to persistent storage * @var integer how many messages should be accumulated before they are exported.
* (e.g. DB, email) whenever [[processMessages()]] is called. Defaults to true. If false, * Defaults to 1000. Note that messages will always be exported when the application terminates.
* the collected messages will be stored in [[messages]] without any further processing. * Set this property to be 0 if you don't want to export messages until the application terminates.
*/ */
public $autoExport = true; public $exportInterval = 1000;
/** /**
* @var array the messages that are retrieved from the logger so far by this log target. * @var array the messages that are retrieved from the logger so far by this log target.
* @see autoExport * @see autoExport
*/ */
public $messages = array(); public $messages = array();
private $_levels = 0;
/** /**
* Exports log messages to a specific destination. * Exports log messages to a specific destination.
* Child classes must implement this method. Note that you may need * Child classes must implement this method. Note that you may need
...@@ -102,7 +104,8 @@ abstract class Target extends \yii\base\Component ...@@ -102,7 +104,8 @@ abstract class Target extends \yii\base\Component
$messages = $this->filterMessages($messages); $messages = $this->filterMessages($messages);
$this->messages = array_merge($this->messages, $messages); $this->messages = array_merge($this->messages, $messages);
if (!empty($this->messages) && ($this->autoExport || $final)) { $count = count($this->messages);
if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
$this->prepareExport($final); $this->prepareExport($final);
$this->exportMessages($final); $this->exportMessages($final);
$this->messages = array(); $this->messages = array();
...@@ -160,6 +163,58 @@ abstract class Target extends \yii\base\Component ...@@ -160,6 +163,58 @@ abstract class Target extends \yii\base\Component
} }
/** /**
* @return integer the message levels that this target is interested in. This is a bitmap of
* level values. Defaults to 0, meaning all available levels.
*/
public function getLevels()
{
return $this->_levels;
}
/**
* Sets the message levels that this target is interested in.
*
* The parameter can be either an array of interested level names or an integer representing
* the bitmap of the interested level values. Valid level names include: 'error',
* 'warning', 'info', 'trace' and 'profile'; valid level values include:
* [[Logger::LEVEL_ERROR]], [[Logger::LEVEL_WARNING]], [[Logger::LEVEL_INFO]],
* [[Logger::LEVEL_TRACE]] and [[Logger::LEVEL_PROFILE]].
*
* For example,
*
* ~~~
* array('error', 'warning')
* // which is equivalent to:
* Logger::LEVEL_ERROR | Logger::LEVEL_WARNING
* ~~~
*
* @param array|integer $levels message levels that this target is interested in.
* @throws InvalidConfigException if an unknown level name is given
*/
public function setLevels($levels)
{
static $levelMap = array(
'error' => Logger::LEVEL_ERROR,
'warning' => Logger::LEVEL_WARNING,
'info' => Logger::LEVEL_INFO,
'trace' => Logger::LEVEL_TRACE,
'profile' => Logger::LEVEL_PROFILE,
);
if (is_array($levels)) {
$this->_levels = 0;
foreach ($levels as $level) {
if (isset($levelMap[$level])) {
$this->_levels |= $levelMap[$level];
} else {
throw new InvalidConfigException("Unrecognized level: $level");
}
}
} else {
$this->_levels = $levels;
}
}
/**
* Filters the given messages according to their categories and levels. * Filters the given messages according to their categories and levels.
* @param array $messages messages to be filtered * @param array $messages messages to be filtered
* @return array the filtered messages. * @return array the filtered messages.
...@@ -168,8 +223,10 @@ abstract class Target extends \yii\base\Component ...@@ -168,8 +223,10 @@ abstract class Target extends \yii\base\Component
*/ */
protected function filterMessages($messages) protected function filterMessages($messages)
{ {
$levels = $this->getLevels();
foreach ($messages as $i => $message) { foreach ($messages as $i => $message) {
if (!empty($this->levels) && !in_array($message[1], $this->levels)) { if ($levels && !($levels & $message[1])) {
unset($messages[$i]); unset($messages[$i]);
continue; continue;
} }
...@@ -210,7 +267,19 @@ abstract class Target extends \yii\base\Component ...@@ -210,7 +267,19 @@ abstract class Target extends \yii\base\Component
*/ */
public function formatMessage($message) public function formatMessage($message)
{ {
$s = is_string($message[0]) ? $message[0] : var_export($message[0], true); static $levels = array(
return date('Y/m/d H:i:s', $message[3]) . " [{$message[1]}] [{$message[2]}] $s\n"; Logger::LEVEL_ERROR => 'error',
Logger::LEVEL_WARNING => 'warning',
Logger::LEVEL_INFO => 'info',
Logger::LEVEL_TRACE => 'trace',
Logger::LEVEL_PROFILE_BEGIN => 'profile begin',
Logger::LEVEL_PROFILE_END => 'profile end',
);
list($text, $level, $category, $timestamp) = $message;
$level = isset($levels[$level]) ? $levels[$level] : 'unknown';
if (!is_string($text)) {
$text = var_export($text, true);
}
return date('Y/m/d H:i:s', $timestamp) . " [$level] [$category] $text\n";
} }
} }
- db
* pgsql, sql server, oracle, db2 drivers
* write a guide on creating own schema definitions
* document-based (should allow storage-specific methods additionally to generic ones)
* mongodb (put it under framework/db/mongodb)
* key-value-based (should allow storage-specific methods additionally to generic ones)
* redis (put it under framework/db/redis or perhaps framework/caching?)
- logging - logging
* WebTarget * WebTarget
* ProfileTarget * ProfileTarget
- caching
* a console command to clear cached data
---
- base - base
* module * module
- Module should be able to define its own configuration including routes. Application should be able to overwrite it. - Module should be able to define its own configuration including routes. Application should be able to overwrite it.
...@@ -16,17 +28,6 @@ ...@@ -16,17 +28,6 @@
* support for markdown syntax * support for markdown syntax
* support for [[name]] * support for [[name]]
* consider to be released as a separate tool for user app docs * consider to be released as a separate tool for user app docs
- caching
* a way to invalidate/clear cached data
* a command to clear cached data
- db
* pgsql, sql server, oracle, db2 drivers
* write a guide on creating own schema definitions
* document-based (should allow storage-specific methods additionally to generic ones)
* mongodb
* key-value-based (should allow storage-specific methods additionally to generic ones)
* redis
* memcachedb
- i18n - i18n
* consider using PHP built-in support and data * consider using PHP built-in support and data
* message translations, choice format * message translations, choice format
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment