Browse Source

w

tags/2.0.0-beta
Qiang Xue 14 years ago
parent
commit
6d2e0ba63d
  1. 7
      framework/YiiBase.php
  2. 5
      framework/base/Component.php
  3. 19
      framework/base/Initable.php
  4. 12
      framework/base/Model.php
  5. 142
      framework/logging/EmailTarget.php
  6. 107
      framework/logging/Filter.php
  7. 190
      framework/logging/Logger.php
  8. 5
      framework/logging/Router.php
  9. 239
      framework/logging/Target.php

7
framework/YiiBase.php

@ -160,6 +160,9 @@ class YiiBase
* In the latter case, the root alias will be replaced by the corresponding registered path * In the latter case, the root alias will be replaced by the corresponding registered path
* and the remaining part will be appended to it. * 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. * Note, this method does not ensure the existence of the resulting path.
* @param string $alias alias * @param string $alias alias
* @return mixed path corresponding to the alias, false if the root alias is not previously registered. * @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])) { if (isset(self::$aliases[$rootAlias])) {
return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos); return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos);
} }
else if($alias[0] !== '@') { // not an alias
return $alias;
}
} }
return false; return false;
} }
@ -344,7 +350,6 @@ class YiiBase
} }
if ($object instanceof Initable) { if ($object instanceof Initable) {
$object->preinit();
foreach ($config as $name => $value) { foreach ($config as $name => $value) {
$object->$name = $value; $object->$name = $value;
} }

5
framework/base/Component.php

@ -307,9 +307,9 @@ class Component
* Creates a new component instance. * Creates a new component instance.
* *
* This method differs from the PHP `new` operator in that it does the following * 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 * - Initialize the component properties using the name-value pairs given as the
* last parameter to this method; * last parameter to this method;
* - Call [[Initable::init|init]] if the class implements [[Initable]]. * - Call [[Initable::init|init]] if the class implements [[Initable]].
@ -329,7 +329,6 @@ class Component
* $model = Foo::create(1, 2, array('c' => 3)); * $model = Foo::create(1, 2, array('c' => 3));
* // which is equivalent to the following lines: * // which is equivalent to the following lines:
* $model = new Foo(1, 2); * $model = new Foo(1, 2);
* $model->preinit();
* $model->c = 3; * $model->c = 3;
* $model->init(); * $model->init();
* ~~~ * ~~~

19
framework/base/Initable.php

@ -13,9 +13,9 @@ namespace yii\base;
* Initable is an interface indicating a class needs initialization to work properly. * Initable is an interface indicating a class needs initialization to work properly.
* *
* Initable requires a class to implement the [[init]] method. * Initable requires a class to implement the [[init]] method.
* When [[\Yii::createComponent]] is creating a new component instance, if the component * When [[\Yii::createComponent]] is being used to create a new component which implements
* class implements Initable interface, the method will call its [[init]] method * Initable, it will call the [[init]] method after setting the initial values of the
* after setting the initial values of the component properties. * component properties.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
@ -23,23 +23,10 @@ namespace yii\base;
interface Initable 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. * Initializes this component.
* This method is invoked by [[\Yii::createComponent]] after its creates the new * This method is invoked by [[\Yii::createComponent]] after its creates the new
* component instance and initializes the component properties. In other words, * component instance and initializes the component properties. In other words,
* at this stage, the component has been fully configured. * 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(); public function init();
} }

12
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. * Initializes this model.
* *
* This method is required by the [[Initable]] interface. It is invoked by [[\Yii::createComponent]] * This method is required by the [[Initable]] interface. It is invoked by [[\Yii::createComponent]]

142
framework/logging/EmailTarget.php

@ -1,146 +1,76 @@
<?php <?php
/** /**
* CEmailLogRoute class file. * EmailTarget class file.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC * @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @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. * The target email addresses may be specified via [[emails]] property.
* Optionally, you may set the email {@link setSubject subject}, the * Optionally, you may set the email [[subject]], [[sentFrom]] address and
* {@link setSentFrom sentFrom} address and any additional {@link setHeaders headers}. * additional [[headers]].
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CEmailLogRoute.php 3001 2011-02-24 16:42:44Z alexander.makarow $ * @since 2.0
* @package system.logging
* @since 1.0
*/ */
class CEmailLogRoute extends CLogRoute class EmailTarget extends Target
{ {
/** /**
* @var array list of destination email addresses. * @var array list of destination email addresses.
*/ */
private $_email = array(); public $emails = array();
/** /**
* @var string email subject * @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. * @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. * Sends log [[messages]] to specified email addresses.
* @param array $logs list of log messages * @param boolean $final whether this method is called at the end of the current application
*/ */
protected function processLogs($logs) public function exportMessages($final)
{ {
$message = ''; $body = '';
foreach ($logs as $log) foreach ($this->messages as $message) {
$message .= $this->formatLogMessage($log[0], $log[1], $log[2], $log[3]); $body .= $this->formatMessage($message);
$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 = wordwrap($body, 70);
/** $subject = $this->subject === null ? Yii::t('yii', 'Application Log') : $this->subject;
* Sends an email. foreach ($this->emails as $email) {
* @param string $email single email address $this->sendEmail($subject, $body, $email, $this->sentFrom, $this->headers);
* @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));
} }
/** $this->messages = array();
* @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 * Sends an email.
* a string, it is assumed to be comma-separated email addresses. * @param string $subject email subject
*/ * @param string $body email body
public function setEmails($value) * @param string $sentTo sent-to email address
{ * @param string $sentFrom sent-from email address
if (is_array($value)) * @param array $headers additional headers to be used when sending the email
$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() protected function sendEmail($subject, $body, $sentTo, $sentFrom, $headers)
{ {
return $this->_headers; if ($sentFrom !== null) {
$headers[] = "From: {$sentFrom}";
} }
mail($email, $subject, $body, implode("\r\n", $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);
} }
} }

107
framework/logging/Filter.php

@ -1,107 +0,0 @@
<?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);
}
}

190
framework/logging/Logger.php

@ -38,20 +38,26 @@ class Logger extends \yii\base\Component
public $flushInterval = 1000; public $flushInterval = 1000;
/** /**
* @var boolean this property will be passed as the parameter to [[flush]] when it is * @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 * called due to the [[flushInterval]] is reached. Defaults to true, meaning the flushed
* messages are still kept in the memory by each log target. If this is true, they will * messages will be exported to the actual storage medium (e.g. DB, email) defined by each
* be exported to the actual storage medium (e.g. DB, email) defined by each log target. * log target. If false, the flushed messages will be kept in the memory of each log target.
* @see flushInterval * @see flushInterval
*/ */
public $autoExport = false; public $autoExport = true;
/** /**
* @var array logged messages. This property is mainly managed by [[log]] and [[flush]]. * @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(); public $messages = array();
/**
* @var array the profiling results (category, token => time in seconds)
*/
private $_timings;
/** /**
* Logs an error message. * Logs an error message.
@ -155,72 +161,24 @@ class Logger extends \yii\base\Component
} }
/** /**
* Retrieves log messages. * Removes all recorded messages from the memory.
* * This method will raise an {@link onFlush} event.
* Messages may be filtered by log levels and/or categories. * The attached event handlers can process the log messages before they are removed.
* A level filter is specified by a list of levels separated by comma or space * @param boolean $export whether to notify log targets to export the filtered messages they have received.
* (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) public function flush($export = false)
{
foreach ($this->_categories as $category)
{ {
$cat = strtolower($value[2]); $this->onFlush(new \yii\base\Event($this, array('export' => $export, 'flush' => true)));
if ($cat === $category || (($c = rtrim($category, '.*')) !== $category && strpos($cat, $c) === 0)) $this->messages = array();
return $value;
}
return false;
} }
/** /**
* Filter function used by {@link getLogs} * Raises an `onFlush` event.
* @param array $value element to be filtered * @param \yii\base\Event $event the event parameter
* @return array valid log, false if not.
*/ */
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. * 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 * By default, all profiling results will be returned. You may provide
* being `array($token, $category, $time)`. * `$categories` and `$excludeCategories` as parameters to retrieve the
* If a filter is specified, the results would be an array of timings. * results that you are interested in.
* @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 array $categories list of categories that you are interested in.
* @param boolean $refresh whether to refresh the internal timing calculations. If false, * You can use an asterisk at the end of a category to do a prefix match.
* only the first time calling this method will the timings be calculated internally. * For example, 'yii\db\*' will match categories starting with 'yii\db\',
* @return array the profiling results. * 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) { $timings = $this->calculateTimings();
$this->calculateTimings(); if (empty($categories) && empty($excludeCategories)) {
return $timings;
}
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 ($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)) { if (!$matched) {
$results[] = $timing[2]; unset($timings[$i]);
} }
} }
return $results; return array_values($timings);
} }
private function calculateTimings() private function calculateTimings()
{ {
$this->_timings = array(); $timings = array();
$stack = array(); $stack = array();
foreach ($this->messages as $log) { foreach ($this->messages as $log) {
@ -274,18 +254,16 @@ class Logger extends \yii\base\Component
continue; continue;
} }
list($message, $level, $category, $timestamp) = $log; list($message, $level, $category, $timestamp) = $log;
if (!strncasecmp($message, 'begin:', 6)) { if ($message === 'begin') {
$log[0] = substr($message, 6);
$stack[] = $log; $stack[] = $log;
} }
elseif (!strncasecmp($message, 'end:', 4)) { else { // $message === 'end'
$token = substr($message, 4); if (($last = array_pop($stack)) !== null && $last[2] === $category) {
if (($last = array_pop($stack)) !== null && $last[0] === $token) { $delta = $timestamp - $last[3];
$delta = $log[3] - $last[3]; $timings[] = array($category, $delta);
$this->_timings[] = array($message, $category, $delta);
} }
else { 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); $now = microtime(true);
while (($last = array_pop($stack)) !== null) { while (($last = array_pop($stack)) !== null) {
$delta = $now - $last[3]; $delta = $now - $last[3];
$this->_timings[] = array($last[0], $last[2], $delta); $timings[] = array($last[2], $delta);
}
} }
/** return $timings;
* 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);
}
} }

5
framework/logging/Router.php

@ -125,11 +125,12 @@ class Router extends \yii\base\ApplicationComponent
*/ */
public function processMessages($event) public function processMessages($event)
{ {
$logger = Yii::getLogger(); $messages = \Yii::getLogger()->messages;
$export = !isset($event->params['export']) || $event->params['export']; $export = !isset($event->params['export']) || $event->params['export'];
$final = !isset($event-params['flush']) || !$event->params['flush'];
foreach ($this->_targets as $target) { foreach ($this->_targets as $target) {
if ($target->enabled) { if ($target->enabled) {
$target->processMessages($logger, $export); $target->processMessages($messages, $export, $final);
} }
} }
} }

239
framework/logging/Target.php

@ -13,18 +13,15 @@ namespace yii\logging;
/** /**
* Target is the base class for all log target classes. * Target is the base class for all log target classes.
* *
* A log target object retrieves log messages from a logger and sends it * A log target object will filter the messages logged by [[Logger]] according
* somewhere, such as files, emails. * to its [[levels]] and [[categories]] properties. It may also export the filtered
* The messages being retrieved may be filtered first before being sent * messages to specific destination defined by the target, such as emails, files.
* 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 * 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 <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
@ -36,42 +33,57 @@ abstract class Target extends \yii\base\Component implements \yii\base\Initable
*/ */
public $enabled = true; 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 $levels; public $excludeCategories = array();
/** /**
* @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 session ID. Defaults to false.
*/ */
public $categories; public $prefixSession = false;
/** /**
* @var string list of categories that should be excluded. * @var boolean whether to prefix each log message with the current user name and ID. Defaults to false.
* @see \yii\web\User
*/ */
public $excludeCategories; public $prefixUser = false;
/** /**
* @var mixed the additional filter (eg {@link CLogFilter}) that can be applied to the log messages. * @var boolean whether to log a message containing the current user name and ID. Defaults to true.
* The value of this property will be passed to {@link Yii::createComponent} to create * @see \yii\web\User
* 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; public $logUser = false;
/** /**
* @var array the messages that are collected so far by this log target. * @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 $messages; public $logVars = array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER');
/**
* @var array the messages that are retrieved from the logger so far by this log target.
*/
public $messages = array();
/** /**
* Pre-initializes this component. * Exports log messages to a specific destination.
* This method is required by the [[Initable]] interface. It is invoked by * Child classes must implement this method. Note that you may need
* [[\Yii::createComponent]] after its creates the new component instance but * to clean up [[messages]] in this method to avoid re-exporting messages.
* BEFORE the component properties are initialized. * @param boolean $final whether this method is called at the end of the current application
*
* You may override this method to do work such as setting property default values.
*/ */
public function preinit() abstract public function exportMessages($final);
{
}
/** /**
* Initializes this component. * 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. * Processes the given log messages.
* @param string $message message content * This method will filter the given messages with [[levels]] and [[categories]].
* @param integer $level message level * And if requested, it will also export the filtering result to specific medium (e.g. email).
* @param string $category message category * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure
* @param integer $time timestamp * of each message.
* @return string formatted 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. * Prepares the [[messages]] for exporting.
* @param CLogger $logger logger instance * This method will modify each message by prependding extra information
* @param boolean $processLogs whether to process the messages after they are collected from the logger * 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) $prefix = array();
Yii::createComponent($this->filter)->filter($this->messages); if ($this->prefixSession && ($id = session_id()) !== '') {
$this->processLogs($this->messages); $prefix[] = "[$id]";
$this->messages = array();
} }
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];
} }
}
protected function filterMessages($levels = '', $categories = '') if ($final && ($context = $this->getContextMessage()) !== '') {
{ $this->messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME);
$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} * Generates the context information to be logged.
* @param array $value element to be filtered * The default implementation will dump user information, system variables, etc.
* @return array valid log, false if not. * @return string the context information. If an empty string, it means no context information.
*/ */
protected function filterByCategory($value) protected function getContextMessage()
{
foreach ($this->_categories as $category)
{ {
$cat = strtolower($value[2]); $context = array();
if ($cat === $category || (($c = rtrim($category, '.*')) !== $category && strpos($cat, $c) === 0)) if ($this->logUser && ($user = \Yii::app()->getComponent('user', false)) !== null) {
return $value; $context[] = 'User: ' . $user->getName() . ' (ID: ' . $user->getId() . ')';
}
foreach ($this->logVars as $name) {
if (!empty($GLOBALS[$name])) {
$context[] = "\${$name} = " . var_export($GLOBALS[$name], true);
} }
return false; }
return implode("\n\n", $context);
} }
/** /**
* Filter function used by {@link getLogs} * Filters the given messages according to their categories and levels.
* @param array $value element to be filtered * @param array $messages messages to be filtered
* @return array valid log, false if not. * @return array the filtered messages.
* @see filterByCategory
* @see filterByLevel
*/ */
protected function filterByLevel($value) protected function filterMessages($messages)
{ {
return in_array(strtolower($value[1]), $this->_levels) ? $value : false; 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 $messages;
} }
/** /**
* Processes log messages and sends them to specific destination. * Formats a log message.
* Derived child classes must implement this method. * The message structure follows that in [[Logger::messages]].
* @param array $messages list of messages. Each array elements represents one message * @param array $message the log message to be formatted.
* with the following structure: * @return string the formatted message
* array( */
* [0] => message (string) public function formatMessage($message)
* [1] => level (string) {
* [2] => category (string) return @date('Y/m/d H:i:s', $message[3]) . " [{$message[1]}] [{$message[2]}] {$message[0]}\n";
* [3] => timestamp (float, obtained by microtime(true)); }
*/
abstract protected function processLogs($messages);
} }

Loading…
Cancel
Save