From 6d2e0ba63d41a09127d2b4b57959ddc1571f0243 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 4 Aug 2011 21:28:12 -0400 Subject: [PATCH] w --- framework/YiiBase.php | 7 +- framework/base/Component.php | 5 +- framework/base/Initable.php | 19 +-- framework/base/Model.php | 12 -- framework/logging/EmailTarget.php | 146 ++++++----------------- framework/logging/Filter.php | 107 ----------------- framework/logging/Logger.php | 192 ++++++++++++------------------ framework/logging/Router.php | 5 +- framework/logging/Target.php | 239 +++++++++++++++++++++++--------------- 9 files changed, 272 insertions(+), 460 deletions(-) delete mode 100644 framework/logging/Filter.php diff --git a/framework/YiiBase.php b/framework/YiiBase.php index bc5b2cc..b3981d5 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -160,6 +160,9 @@ class YiiBase * In the latter case, the root alias will be replaced by the corresponding registered path * and the remaining part will be appended to it. * + * In case the given alias is not an alias (i.e., not starting with '@'), + * it will be returned back as is. + * * Note, this method does not ensure the existence of the resulting path. * @param string $alias alias * @return mixed path corresponding to the alias, false if the root alias is not previously registered. @@ -175,6 +178,9 @@ class YiiBase if (isset(self::$aliases[$rootAlias])) { return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos); } + else if($alias[0] !== '@') { // not an alias + return $alias; + } } return false; } @@ -344,7 +350,6 @@ class YiiBase } if ($object instanceof Initable) { - $object->preinit(); foreach ($config as $name => $value) { $object->$name = $value; } diff --git a/framework/base/Component.php b/framework/base/Component.php index 8619a39..8589e7d 100644 --- a/framework/base/Component.php +++ b/framework/base/Component.php @@ -307,9 +307,9 @@ class Component * Creates a new component instance. * * This method differs from the PHP `new` operator in that it does the following - * additional work after the component is created: + * steps to create a new component instance: * - * - Call [[Initable::preinit|preinit]] if the class implements [[Initable]]; + * - Call class constructor (same the `new` operator); * - Initialize the component properties using the name-value pairs given as the * last parameter to this method; * - Call [[Initable::init|init]] if the class implements [[Initable]]. @@ -329,7 +329,6 @@ class Component * $model = Foo::create(1, 2, array('c' => 3)); * // which is equivalent to the following lines: * $model = new Foo(1, 2); - * $model->preinit(); * $model->c = 3; * $model->init(); * ~~~ diff --git a/framework/base/Initable.php b/framework/base/Initable.php index 02b397b..c91cb03 100644 --- a/framework/base/Initable.php +++ b/framework/base/Initable.php @@ -13,9 +13,9 @@ namespace yii\base; * Initable is an interface indicating a class needs initialization to work properly. * * Initable requires a class to implement the [[init]] method. - * When [[\Yii::createComponent]] is creating a new component instance, if the component - * class implements Initable interface, the method will call its [[init]] method - * after setting the initial values of the component properties. + * When [[\Yii::createComponent]] is being used to create a new component which implements + * Initable, it will call the [[init]] method after setting the initial values of the + * component properties. * * @author Qiang Xue * @since 2.0 @@ -23,23 +23,10 @@ namespace yii\base; interface Initable { /** - * Pre-initializes this component. - * This method is invoked by [[\Yii::createComponent]] after its creates the new - * component instance, but BEFORE the component properties are initialized. - * - * You may implement this method to do work such as setting property default values. - */ - public function preinit(); - - /** * Initializes this component. * This method is invoked by [[\Yii::createComponent]] after its creates the new * component instance and initializes the component properties. In other words, * at this stage, the component has been fully configured. - * - * The default implementation calls [[behaviors]] and registers any available behaviors. - * You may override this method with additional initialization logic (e.g. establish DB connection). - * Make sure you call the parent implementation. */ public function init(); } diff --git a/framework/base/Model.php b/framework/base/Model.php index a21efed..918a9df 100644 --- a/framework/base/Model.php +++ b/framework/base/Model.php @@ -50,18 +50,6 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc } /** - * Pre-initializes this model. - * This method is required by the [[Initable]] interface. It is invoked by - * [[\Yii::createComponent]] after its creates the new model instance but - * BEFORE the model properties are initialized. - * - * You may override this method to do work such as setting property default values. - */ - public function preinit() - { - } - - /** * Initializes this model. * * This method is required by the [[Initable]] interface. It is invoked by [[\Yii::createComponent]] diff --git a/framework/logging/EmailTarget.php b/framework/logging/EmailTarget.php index c6a381a..3e9136f 100644 --- a/framework/logging/EmailTarget.php +++ b/framework/logging/EmailTarget.php @@ -1,146 +1,76 @@ * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC + * @copyright Copyright © 2008-2012 Yii Software LLC * @license http://www.yiiframework.com/license/ */ +namespace yii\logging; + /** - * CEmailLogRoute sends selected log messages to email addresses. + * EmailTarget sends selected log messages to the specified 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}. + * The target email addresses may be specified via [[emails]] property. + * Optionally, you may set the email [[subject]], [[sentFrom]] address and + * additional [[headers]]. * * @author Qiang Xue - * @version $Id: CEmailLogRoute.php 3001 2011-02-24 16:42:44Z alexander.makarow $ - * @package system.logging - * @since 1.0 + * @since 2.0 */ -class CEmailLogRoute extends CLogRoute +class EmailTarget extends Target { /** * @var array list of destination email addresses. */ - private $_email = array(); + public $emails = array(); /** * @var string email subject */ - private $_subject; + public $subject; /** - * @var string email sent from address + * @var string email sent-from address */ - private $_from; + public $sentFrom; /** * @var array list of additional headers to use when sending an email. */ - private $_headers = array(); + public $headers = array(); /** - * Sends log messages to specified email addresses. - * @param array $logs list of log messages + * Sends log [[messages]] to specified email addresses. + * @param boolean $final whether this method is called at the end of the current application */ - protected function processLogs($logs) + public function exportMessages($final) { - $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); + $body = ''; + foreach ($this->messages as $message) { + $body .= $this->formatMessage($message); + } + $body = wordwrap($body, 70); + $subject = $this->subject === null ? Yii::t('yii', 'Application Log') : $this->subject; + foreach ($this->emails as $email) { + $this->sendEmail($subject, $body, $email, $this->sentFrom, $this->headers); + } + + $this->messages = array(); } /** * 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 + * @param string $body email body + * @param string $sentTo sent-to email address + * @param string $sentFrom sent-from email address + * @param array $headers additional headers to be used when sending the email */ - public function setHeaders($value) + protected function sendEmail($subject, $body, $sentTo, $sentFrom, $headers) { - if (is_array($value)) - $this->_headers = $value; - else - $this->_headers = preg_split('/\r\n|\n/', $value, -1, PREG_SPLIT_NO_EMPTY); + if ($sentFrom !== null) { + $headers[] = "From: {$sentFrom}"; + } + mail($email, $subject, $body, implode("\r\n", $headers)); } } \ No newline at end of file diff --git a/framework/logging/Filter.php b/framework/logging/Filter.php deleted file mode 100644 index ab436e4..0000000 --- a/framework/logging/Filter.php +++ /dev/null @@ -1,107 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -/** - * CLogFilter preprocesses the logged messages before they are handled by a log route. - * - * CLogFilter is meant to be used by a log route to preprocess the logged messages - * before they are handled by the route. The default implementation of CLogFilter - * prepends additional context information to the logged messages. In particular, - * by setting {@link logVars}, predefined PHP variables such as - * $_SERVER, $_POST, etc. can be saved as a log message, which may help identify/debug - * issues encountered. - * - * @author Qiang Xue - * @version $Id: CLogFilter.php 3204 2011-05-05 21:36:32Z alexander.makarow $ - * @package system.logging - * @since 1.0.6 - */ -class CLogFilter extends CComponent -{ - /** - * @var boolean whether to prefix each log message with the current user session ID. - * Defaults to false. - */ - public $prefixSession = false; - /** - * @var boolean whether to prefix each log message with the current user - * {@link CWebUser::name name} and {@link CWebUser::id ID}. Defaults to false. - */ - public $prefixUser = false; - /** - * @var boolean whether to log the current user name and ID. Defaults to true. - */ - public $logUser = true; - /** - * @var array list of the PHP predefined variables that should be logged. - * Note that a variable must be accessible via $GLOBALS. Otherwise it won't be logged. - */ - public $logVars = array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'); - - - /** - * Filters the given log messages. - * This is the main method of CLogFilter. It processes the log messages - * by adding context information, etc. - * @param array $logs the log messages - * @return array - */ - public function filter(&$logs) - { - if (!empty($logs)) - { - if (($message = $this->getContext()) !== '') - array_unshift($logs, array($message, CLogger::LEVEL_INFO, 'application', YII_BEGIN_TIME)); - $this->format($logs); - } - return $logs; - } - - /** - * Formats the log messages. - * The default implementation will prefix each message with session ID - * if {@link prefixSession} is set true. It may also prefix each message - * with the current user's name and ID if {@link prefixUser} is true. - * @param array $logs the log messages - */ - protected function format(&$logs) - { - $prefix = ''; - if ($this->prefixSession && ($id = session_id()) !== '') - $prefix .= "[$id]"; - if ($this->prefixUser && ($user = Yii::app()->getComponent('user', false)) !== null) - $prefix .= '[' . $user->getName() . '][' . $user->getId() . ']'; - if ($prefix !== '') - { - foreach ($logs as &$log) - $log[0] = $prefix . ' ' . $log[0]; - } - } - - /** - * Generates the context information to be logged. - * The default implementation will dump user information, system variables, etc. - * @return string the context information. If an empty string, it means no context information. - */ - protected function getContext() - { - $context = array(); - if ($this->logUser && ($user = Yii::app()->getComponent('user', false)) !== null) - $context[] = 'User: ' . $user->getName() . ' (ID: ' . $user->getId() . ')'; - - foreach ($this->logVars as $name) - { - if (!empty($GLOBALS[$name])) - $context[] = "\$ {$name}=" . var_export($GLOBALS[$name], true); - } - - return implode("\n\n", $context); - } -} \ No newline at end of file diff --git a/framework/logging/Logger.php b/framework/logging/Logger.php index e09092b..056a72e 100644 --- a/framework/logging/Logger.php +++ b/framework/logging/Logger.php @@ -38,20 +38,26 @@ class Logger extends \yii\base\Component 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. + * called due to the [[flushInterval]] is reached. Defaults to true, meaning the flushed + * messages will be exported to the actual storage medium (e.g. DB, email) defined by each + * log target. If false, the flushed messages will be kept in the memory of each log target. * @see flushInterval */ - public $autoExport = false; + public $autoExport = true; /** * @var array logged messages. This property is mainly managed by [[log]] and [[flush]]. + * Each log message is of the following structure: + * + * ~~~ + * array( + * [0] => message (string) + * [1] => level (string) + * [2] => category (string) + * [3] => timestamp (float, obtained by microtime(true)) + * ) + * ~~~ */ public $messages = array(); - /** - * @var array the profiling results (category, token => time in seconds) - */ - private $_timings; /** * Logs an error message. @@ -155,72 +161,24 @@ class Logger extends \yii\base\Component } /** - * 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. + * 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. */ - private function filterByCategory($value) + public function flush($export = false) { - foreach ($this->_categories as $category) - { - $cat = strtolower($value[2]); - if ($cat === $category || (($c = rtrim($category, '.*')) !== $category && strpos($cat, $c) === 0)) - return $value; - } - return false; + $this->onFlush(new \yii\base\Event($this, array('export' => $export, 'flush' => true))); + $this->messages = array(); } /** - * Filter function used by {@link getLogs} - * @param array $value element to be filtered - * @return array valid log, false if not. + * Raises an `onFlush` event. + * @param \yii\base\Event $event the event parameter */ - private function filterByLevel($value) + public function onFlush($event) { - return in_array(strtolower($value[1]), $this->_levels) ? $value : false; + $this->raiseEvent('onFlush', $event); } /** @@ -237,36 +195,58 @@ class Logger extends \yii\base\Component /** * 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. + * + * By default, all profiling results will be returned. You may provide + * `$categories` and `$excludeCategories` as parameters to retrieve the + * results that you are interested in. + * + * @param array $categories list of categories that you are interested in. + * You can use an asterisk at the end of a category to do a prefix match. + * For example, 'yii\db\*' will match categories starting with 'yii\db\', + * such as 'yii\db\dao\Connection'. + * @param array $excludeCategories list of categories that you are interested in. + * @return array the profiling results. Each array element has the following structure: + * `array($category, $time)`. */ - public function getProfilingResults($token = null, $category = null, $refresh = false) + public function getProfiling($categories = array(), $excludeCategories = array()) { - if ($this->_timings === null || $refresh) { - $this->calculateTimings(); - } - if ($token === null && $category === null) { - return $this->_timings; + $timings = $this->calculateTimings(); + if (empty($categories) && empty($excludeCategories)) { + return $timings; } - $results = array(); - foreach ($this->_timings as $timing) { - if (($category === null || $timing[1] === $category) && ($token === null || $timing[0] === $token)) { - $results[] = $timing[2]; + + foreach ($timings as $i => $timing) { + $matched = empty($categories); + foreach ($categories as $category) { + $prefix = rtrim($category, '*'); + if (strpos($timing[0], $prefix) === 0 && ($timing[0] === $category || $prefix !== $category)) { + $matched = true; + break; + } + } + + if ($matched) { + foreach ($excludeCategories as $category) { + $prefix = rtrim($category, '*'); + foreach ($timings as $i => $timing) { + if (strpos($timing[0], $prefix) === 0 && ($timing[0] === $category || $prefix !== $category)) { + $matched = false; + break; + } + } + } + } + + if (!$matched) { + unset($timings[$i]); } } - return $results; + return array_values($timings); } private function calculateTimings() { - $this->_timings = array(); + $timings = array(); $stack = array(); foreach ($this->messages as $log) { @@ -274,18 +254,16 @@ class Logger extends \yii\base\Component continue; } list($message, $level, $category, $timestamp) = $log; - if (!strncasecmp($message, 'begin:', 6)) { - $log[0] = substr($message, 6); + if ($message === 'begin') { $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 { // $message === 'end' + if (($last = array_pop($stack)) !== null && $last[2] === $category) { + $delta = $timestamp - $last[3]; + $timings[] = array($category, $delta); } else { - throw new \yii\base\Exception('Found a mismatching profiling block: ' . $token); + throw new \yii\base\Exception('Found a mismatching profiling block: ' . $category); } } } @@ -293,28 +271,10 @@ class Logger extends \yii\base\Component $now = microtime(true); while (($last = array_pop($stack)) !== null) { $delta = $now - $last[3]; - $this->_timings[] = array($last[0], $last[2], $delta); + $timings[] = array($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(); + return $timings; } - /** - * Raises an `onFlush` event. - * @param \yii\base\Event $event the event parameter - */ - public function onFlush($event) - { - $this->raiseEvent('onFlush', $event); - } } diff --git a/framework/logging/Router.php b/framework/logging/Router.php index b0ad8c2..fa08a2c 100644 --- a/framework/logging/Router.php +++ b/framework/logging/Router.php @@ -125,11 +125,12 @@ class Router extends \yii\base\ApplicationComponent */ public function processMessages($event) { - $logger = Yii::getLogger(); + $messages = \Yii::getLogger()->messages; $export = !isset($event->params['export']) || $event->params['export']; + $final = !isset($event-params['flush']) || !$event->params['flush']; foreach ($this->_targets as $target) { if ($target->enabled) { - $target->processMessages($logger, $export); + $target->processMessages($messages, $export, $final); } } } diff --git a/framework/logging/Target.php b/framework/logging/Target.php index b14cfbe..4d20696 100644 --- a/framework/logging/Target.php +++ b/framework/logging/Target.php @@ -13,18 +13,15 @@ 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'). + * A log target object will filter the messages logged by [[Logger]] according + * to its [[levels]] and [[categories]] properties. It may also export the filtered + * messages to specific destination defined by the target, such as emails, files. * * Level filter and category filter are combinational, i.e., only messages - * satisfying both filter conditions will they be returned. + * satisfying both filter conditions will they be returned. Additionally, you + * may specify [[excludeCategories]]. If a message's category falls within the excluded + * categories, it will be filtered out, even if it passes the [[levels]] and + * [[categories]] filters. * * @author Qiang Xue * @since 2.0 @@ -36,42 +33,57 @@ abstract class Target extends \yii\base\Component implements \yii\base\Initable */ public $enabled = true; /** - * @var string list of levels separated by comma or space. Defaults to empty, meaning all levels. + * @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. + * 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 + * categories starting with 'yii\db\', such as 'yii\db\dao\Connection'. + */ + public $categories = array(); + /** + * @var array list of message categories that this target is NOT interested in. Defaults to empty, meaning no uninteresting messages. + * If this property is not empty, then any category listed here will be excluded from [[categories]]. + * You can use an asterisk at the end of a category so that the category can be used to + * match those categories sharing the same common prefix. For example, 'yii\db\*' will match + * categories starting with 'yii\db\', such as 'yii\db\dao\Connection'. + * @see categories + */ + public $excludeCategories = array(); + /** + * @var boolean whether to prefix each log message with the current session ID. Defaults to false. */ - public $levels; + public $prefixSession = false; /** - * @var string list of categories separated by comma or space. Defaults to empty, meaning all categories. + * @var boolean whether to prefix each log message with the current user name and ID. Defaults to false. + * @see \yii\web\User */ - public $categories; + public $prefixUser = false; /** - * @var string list of categories that should be excluded. + * @var boolean whether to log a message containing the current user name and ID. Defaults to true. + * @see \yii\web\User */ - public $excludeCategories; + public $logUser = false; /** - * @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. + * @var array list of the PHP predefined variables that should be logged in a message. + * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be logged. + * Defaults to `array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER')`. */ - public $filter; + public $logVars = array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'); /** - * @var array the messages that are collected so far by this log target. + * @var array the messages that are retrieved from the logger so far by this log target. */ - public $messages; + public $messages = array(); /** - * 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. + * Exports log messages to a specific destination. + * Child classes must implement this method. Note that you may need + * to clean up [[messages]] in this method to avoid re-exporting messages. + * @param boolean $final whether this method is called at the end of the current application */ - public function preinit() - { - } + abstract public function exportMessages($final); /** * Initializes this component. @@ -83,89 +95,126 @@ abstract class Target extends \yii\base\Component implements \yii\base\Initable } /** - * 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 + * Processes the given log messages. + * This method will filter the given messages with [[levels]] and [[categories]]. + * And if requested, it will also export the filtering result to specific medium (e.g. email). + * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure + * of each message. + * @param boolean $export whether to export the processing result + * @param boolean $final whether this method is called at the end of the current application */ - protected function formatMessage($message, $level, $category, $time) + public function processMessages($messages, $export, $final) { - return @date('Y/m/d H:i:s', $time) . " [$level] [$category] $message\n"; + $messages = $this->filterMessages($messages); + $this->messages = array_merge($this->messages, $messages); + + if ($export && !empty($this->messages)) { + $this->prepareExport($final); + $this->exportMessages($final); + } } /** - * 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 + * Prepares the [[messages]] for exporting. + * This method will modify each message by prependding extra information + * if [[prefixSession]] and/or [[prefixUser]] are set true. + * It will also add an additional message showing context information if + * [[logUser]] and/or [[logVars]] are set. + * @param boolean $final whether this method is called at the end of the current application */ - public function processMessages($logger, $export) + protected function prepareExport($final) { - $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(); + $prefix = array(); + if ($this->prefixSession && ($id = session_id()) !== '') { + $prefix[] = "[$id]"; + } + if ($this->prefixUser && ($user = \Yii::app()->getComponent('user', false)) !== null) { + $prefix[] = '[' . $user->getName() . ']'; + $prefix[] = '[' . $user->getId() . ']'; + } + if ($prefix !== array()) { + $prefix = implode(' ', $prefix); + foreach ($this->messages as $i => $message) { + $this->messages[$i][0] = $prefix . ' ' . $this->messages[$i][0]; + } + } + if ($final && ($context = $this->getContextMessage()) !== '') { + $this->messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME); } } - protected function filterMessages($levels = '', $categories = '') + /** + * 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 getContextMessage() { - $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')))); + $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); } /** - * Filter function used by {@link getLogs} - * @param array $value element to be filtered - * @return array valid log, false if not. + * Filters the given messages according to their categories and levels. + * @param array $messages messages to be filtered + * @return array the filtered messages. + * @see filterByCategory + * @see filterByLevel */ - protected function filterByCategory($value) + protected function filterMessages($messages) { - foreach ($this->_categories as $category) - { - $cat = strtolower($value[2]); - if ($cat === $category || (($c = rtrim($category, '.*')) !== $category && strpos($cat, $c) === 0)) - return $value; + foreach ($messages as $i => $message) { + if (!empty($this->levels) && !in_array($message[1], $this->levels)) { + unset($messages[$i]); + continue; + } + + $matched = empty($this->categories); + foreach ($this->categories as $category) { + $prefix = rtrim($category, '*'); + if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) { + $matched = true; + break; + } + } + + if ($matched) { + foreach ($this->excludeCategories as $category) { + $prefix = rtrim($category, '*'); + foreach ($messages as $i => $message) { + if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) { + $matched = false; + break; + } + } + } + } + + if (!$matched) { + unset($messages[$i]); + } } - return false; + return $messages; } /** - * Filter function used by {@link getLogs} - * @param array $value element to be filtered - * @return array valid log, false if not. + * Formats a log message. + * The message structure follows that in [[Logger::messages]]. + * @param array $message the log message to be formatted. + * @return string the formatted message */ - protected function filterByLevel($value) + public function formatMessage($message) { - return in_array(strtolower($value[1]), $this->_levels) ? $value : false; + return @date('Y/m/d H:i:s', $message[3]) . " [{$message[1]}] [{$message[2]}] {$message[0]}\n"; } - - /** - * 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); }