Commit efbd23b5 by Carsten Brandt

refactored logger to support array values out of the box.

fixes #3245, #3244
parent e091195c
...@@ -15,6 +15,9 @@ Basic logging is as simple as calling one method: ...@@ -15,6 +15,9 @@ Basic logging is as simple as calling one method:
\Yii::info('Hello, I am a test log message'); \Yii::info('Hello, I am a test log message');
``` ```
You can log simple strings as well as more complex data structures such as arrays or objects.
When logging data that is not a string the defaulf yii log targets will serialize the value using [[yii\helpers\Vardumper::export()]].
### Message category ### Message category
Additionally to the message itself message category could be specified in order to allow filtering such messages and Additionally to the message itself message category could be specified in order to allow filtering such messages and
......
...@@ -63,7 +63,6 @@ class LogPanel extends Panel ...@@ -63,7 +63,6 @@ class LogPanel extends Panel
{ {
$target = $this->module->logTarget; $target = $this->module->logTarget;
$messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE); $messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE);
return ['messages' => $messages]; return ['messages' => $messages];
} }
......
...@@ -69,7 +69,6 @@ class ProfilingPanel extends Panel ...@@ -69,7 +69,6 @@ class ProfilingPanel extends Panel
{ {
$target = $this->module->logTarget; $target = $this->module->logTarget;
$messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE); $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE);
return [ return [
'memory' => memory_get_peak_usage(), 'memory' => memory_get_peak_usage(),
'time' => microtime(true) - YII_BEGIN_TIME, 'time' => microtime(true) - YII_BEGIN_TIME,
......
...@@ -44,3 +44,6 @@ Upgrade from Yii 2.0 Beta ...@@ -44,3 +44,6 @@ Upgrade from Yii 2.0 Beta
in this release. in this release.
* `yii\console\controllers\AssetController` is now using hashes instead of timestamps. Replace all `{ts}` with `{hash}`. * `yii\console\controllers\AssetController` is now using hashes instead of timestamps. Replace all `{ts}` with `{hash}`.
* The database table of the `yii\log\DbTarget` now needs a `prefix` column to store context information.
You can add it with `ALTER TABLE log ADD COLUMN prefix TEXT AFTER log_time;`.
...@@ -133,6 +133,8 @@ class BaseVarDumper ...@@ -133,6 +133,8 @@ class BaseVarDumper
* This method is similar to `var_export()`. The main difference is that * This method is similar to `var_export()`. The main difference is that
* it generates more compact string representation using short array syntax. * it generates more compact string representation using short array syntax.
* *
* It also handles objects by using the PHP functions serialize() and unserialize().
*
* PHP 5.4 or above is required to parse the exported value. * PHP 5.4 or above is required to parse the exported value.
* *
* @param mixed $var the variable to be exported. * @param mixed $var the variable to be exported.
......
...@@ -16,7 +16,7 @@ use yii\di\Instance; ...@@ -16,7 +16,7 @@ use yii\di\Instance;
* DbTarget stores log messages in a database table. * DbTarget stores log messages in a database table.
* *
* By default, DbTarget stores the log messages in a DB table named 'log'. This table * By default, DbTarget stores the log messages in a DB table named 'log'. This table
* must be pre-created. The table name can be changed by setting [[logTable]]. * must be pre-created. The table name can be changed by setting the [[logTable]] property.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
...@@ -39,6 +39,7 @@ class DbTarget extends Target ...@@ -39,6 +39,7 @@ class DbTarget extends Target
* level INTEGER, * level INTEGER,
* category VARCHAR(255), * category VARCHAR(255),
* log_time INTEGER, * log_time INTEGER,
* prefix TEXT,
* message TEXT, * message TEXT,
* INDEX idx_log_level (level), * INDEX idx_log_level (level),
* INDEX idx_log_category (category) * INDEX idx_log_category (category)
...@@ -55,6 +56,7 @@ class DbTarget extends Target ...@@ -55,6 +56,7 @@ class DbTarget extends Target
*/ */
public $logTable = '{{%log}}'; public $logTable = '{{%log}}';
/** /**
* Initializes the DbTarget component. * Initializes the DbTarget component.
* This method will initialize the [[db]] property to make sure it refers to a valid DB connection. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
...@@ -72,15 +74,20 @@ class DbTarget extends Target ...@@ -72,15 +74,20 @@ class DbTarget extends Target
public function export() public function export()
{ {
$tableName = $this->db->quoteTableName($this->logTable); $tableName = $this->db->quoteTableName($this->logTable);
$sql = "INSERT INTO $tableName ([[level]], [[category]], [[log_time]], [[message]]) $sql = "INSERT INTO $tableName ([[level]], [[category]], [[log_time]], [[prefix]], [[message]])
VALUES (:level, :category, :log_time, :message)"; VALUES (:level, :category, :log_time, :prefix, :message)";
$command = $this->db->createCommand($sql); $command = $this->db->createCommand($sql);
foreach ($this->messages as $message) { foreach ($this->messages as $message) {
list($text, $level, $category, $timestamp) = $message;
if (!is_string($text)) {
$text = var_export($text, true);
}
$command->bindValues([ $command->bindValues([
':level' => $message[1], ':level' => $level,
':category' => $message[2], ':category' => $category,
':log_time' => $message[3], ':log_time' => $timestamp,
':message' => $message[0], ':prefix' => $this->getMessagePrefix($message),
':message' => $text,
])->execute(); ])->execute();
} }
} }
......
...@@ -13,14 +13,14 @@ use yii\base\Component; ...@@ -13,14 +13,14 @@ use yii\base\Component;
/** /**
* Dispatcher manages a set of [[Target|log targets]]. * Dispatcher manages a set of [[Target|log targets]].
* *
* Dispatcher implements [[dispatch()]] that forwards the log messages from [[Logger]] to * Dispatcher implements the [[dispatch()]]-method that forwards the log messages from a [[Logger]] to
* the registered log [[targets]]. * the registered log [[targets]].
* *
* Dispatcher is registered as a core application component and can be accessed using `Yii::$app->log`. * An instance of Dispatcher is registered as a core application component and can be accessed using `Yii::$app->log`.
* *
* You may configure the targets in application configuration, like the following: * You may configure the targets in application configuration, like the following:
* *
* ~~~ * ```php
* [ * [
* 'components' => [ * 'components' => [
* 'log' => [ * 'log' => [
...@@ -41,14 +41,13 @@ use yii\base\Component; ...@@ -41,14 +41,13 @@ use yii\base\Component;
* ], * ],
* ], * ],
* ] * ]
* ~~~ * ```
* *
* Each log target can have a name and can be referenced via the [[targets]] property * Each log target can have a name and can be referenced via the [[targets]] property as follows:
* as follows:
* *
* ~~~ * ```php
* Yii::$app->log->targets['file']->enabled = false; * Yii::$app->log->targets['file']->enabled = false;
* ~~~ * ```
* *
* @property integer $flushInterval How many messages should be logged before they are sent to targets. This * @property integer $flushInterval How many messages should be logged before they are sent to targets. This
* method returns the value of [[Logger::flushInterval]]. * method returns the value of [[Logger::flushInterval]].
...@@ -66,6 +65,7 @@ class Dispatcher extends Component ...@@ -66,6 +65,7 @@ class Dispatcher extends Component
* or the configuration for creating the log target instance. * or the configuration for creating the log target instance.
*/ */
public $targets = []; public $targets = [];
/** /**
* @var Logger the logger. * @var Logger the logger.
*/ */
...@@ -77,6 +77,7 @@ class Dispatcher extends Component ...@@ -77,6 +77,7 @@ class Dispatcher extends Component
*/ */
public function __construct($config = []) public function __construct($config = [])
{ {
// ensure logger gets set before any other config option
if (isset($config['logger'])) { if (isset($config['logger'])) {
$this->setLogger($config['logger']); $this->setLogger($config['logger']);
unset($config['logger']); unset($config['logger']);
......
...@@ -35,6 +35,7 @@ class EmailTarget extends Target ...@@ -35,6 +35,7 @@ class EmailTarget extends Target
*/ */
public $mail = 'mail'; public $mail = 'mail';
/** /**
* @inheritdoc * @inheritdoc
*/ */
......
...@@ -57,6 +57,7 @@ class FileTarget extends Target ...@@ -57,6 +57,7 @@ class FileTarget extends Target
*/ */
public $rotateByCopy = false; public $rotateByCopy = false;
/** /**
* Initializes the route. * Initializes the route.
* This method is invoked after the route is created by the route manager. * This method is invoked after the route is created by the route manager.
......
...@@ -13,7 +13,7 @@ use yii\base\Component; ...@@ -13,7 +13,7 @@ use yii\base\Component;
/** /**
* Logger records logged messages in memory and sends them to different targets if [[dispatcher]] is set. * Logger records logged messages in memory and sends them to different targets if [[dispatcher]] is set.
* *
* Logger can be accessed via `Yii::getLogger()`. You can call the method [[log()]] to record a single log message. * A Logger instance can be accessed via `Yii::getLogger()`. You can call the method [[log()]] to record a single log message.
* For convenience, a set of shortcut methods are provided for logging messages of various severity levels * For convenience, a set of shortcut methods are provided for logging messages of various severity levels
* via the [[Yii]] class: * via the [[Yii]] class:
* *
...@@ -25,7 +25,8 @@ use yii\base\Component; ...@@ -25,7 +25,8 @@ use yii\base\Component;
* - [[Yii::endProfile()]] * - [[Yii::endProfile()]]
* *
* When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]] * When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]]
* to send logged messages to different log targets, such as file, email, Web, with the help of [[dispatcher]]. * to send logged messages to different log targets, such as [[FileTarget|file]], [[EmailTarget|email]],
* or [[DbTarget|database]], with the help of the [[dispatcher]].
* *
* @property array $dbProfiling The first element indicates the number of SQL statements executed, and the * @property array $dbProfiling The first element indicates the number of SQL statements executed, and the
* second element the total time spent in SQL execution. This property is read-only. * second element the total time spent in SQL execution. This property is read-only.
...@@ -122,7 +123,8 @@ class Logger extends Component ...@@ -122,7 +123,8 @@ class Logger extends Component
* Logs a message with the given type and category. * Logs a message with the given type and category.
* If [[traceLevel]] is greater than 0, additional call stack information about * If [[traceLevel]] is greater than 0, additional call stack information about
* the application code will be logged as well. * the application code will be logged as well.
* @param string $message the message to be logged. * @param string|array $message the message to be logged. This can be a simple string or a more
* complex data structure that will be handled by a [[Target|log target]].
* @param integer $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:
* `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`, * `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`,
* `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`. * `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`.
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
namespace yii\log; namespace yii\log;
use Yii; use Yii;
use yii\helpers\VarDumper;
/** /**
* SyslogTarget writes log to syslog. * SyslogTarget writes log to syslog.
...@@ -39,6 +40,7 @@ class SyslogTarget extends Target ...@@ -39,6 +40,7 @@ class SyslogTarget extends Target
Logger::LEVEL_ERROR => LOG_ERR, Logger::LEVEL_ERROR => LOG_ERR,
]; ];
/** /**
* Writes log messages to syslog * Writes log messages to syslog
*/ */
...@@ -59,11 +61,10 @@ class SyslogTarget extends Target ...@@ -59,11 +61,10 @@ class SyslogTarget extends Target
list($text, $level, $category, $timestamp) = $message; list($text, $level, $category, $timestamp) = $message;
$level = Logger::getLevelName($level); $level = Logger::getLevelName($level);
if (!is_string($text)) { if (!is_string($text)) {
$text = var_export($text, true); $text = VarDumper::export($text, true);
} }
$prefix = $this->prefix ? call_user_func($this->prefix, $message) : $this->getMessagePrefix($message); $prefix = $this->getMessagePrefix($message);
return "{$prefix}[$level][$category] $text"; return "{$prefix}[$level][$category] $text";
} }
} }
...@@ -60,9 +60,12 @@ abstract class Target extends Component ...@@ -60,9 +60,12 @@ abstract class Target extends Component
*/ */
public $logVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER']; public $logVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'];
/** /**
* @var callable a PHP callable that returns a string to be prefix to every exported message. * @var callable a PHP callable that returns a string to be prefixed to every exported message.
* If not set, [[getMessagePrefix()]] will be used, which prefixes user IP, user ID and session ID *
* to every message. The signature of the callable should be `function ($message)`. * If not set, [[getMessagePrefix()]] will be used, which prefixes the message with context information
* such as user IP, user ID and session ID.
*
* The signature of the callable should be `function ($message)`.
*/ */
public $prefix; public $prefix;
/** /**
...@@ -79,6 +82,7 @@ abstract class Target extends Component ...@@ -79,6 +82,7 @@ abstract class Target extends Component
private $_levels = 0; private $_levels = 0;
/** /**
* Exports log [[messages]] to a specific destination. * Exports log [[messages]] to a specific destination.
* Child classes must implement this method. * Child classes must implement this method.
...@@ -177,7 +181,8 @@ abstract class Target extends Component ...@@ -177,7 +181,8 @@ abstract class Target extends Component
/** /**
* 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.
* The message structure follows that in [[Logger::messages]].
* @param integer $levels the message levels to filter by. This is a bitmap of * @param integer $levels the message levels to filter by. This is a bitmap of
* level values. Value 0 means allowing all levels. * level values. Value 0 means allowing all levels.
* @param array $categories the message categories to filter by. If empty, it means all categories are allowed. * @param array $categories the message categories to filter by. If empty, it means all categories are allowed.
...@@ -214,14 +219,13 @@ abstract class Target extends Component ...@@ -214,14 +219,13 @@ abstract class Target extends Component
unset($messages[$i]); unset($messages[$i]);
} }
} }
return $messages; return $messages;
} }
/** /**
* Formats a log message. * Formats a log message for display as a string.
* The message structure follows that in [[Logger::messages]].
* @param array $message the log message to be formatted. * @param array $message the log message to be formatted.
* The message structure follows that in [[Logger::messages]].
* @return string the formatted message * @return string the formatted message
*/ */
public function formatMessage($message) public function formatMessage($message)
...@@ -229,30 +233,38 @@ abstract class Target extends Component ...@@ -229,30 +233,38 @@ abstract class Target extends Component
list($text, $level, $category, $timestamp) = $message; list($text, $level, $category, $timestamp) = $message;
$level = Logger::getLevelName($level); $level = Logger::getLevelName($level);
if (!is_string($text)) { if (!is_string($text)) {
$text = var_export($text, true); $text = VarDumper::export($text, true);
} }
$prefix = $this->prefix ? call_user_func($this->prefix, $message) : $this->getMessagePrefix($message); $prefix = $this->getMessagePrefix($message);
return date('Y-m-d H:i:s', $timestamp) . " {$prefix}[$level][$category] $text"; return date('Y-m-d H:i:s', $timestamp) . " {$prefix}[$level][$category] $text";
} }
/** /**
* Returns a string to be prefixed to the given message. * Returns a string to be prefixed to the given message.
* If [[prefix]] is configured it will return the result of the callback.
* The default implementation will return user IP, user ID and session ID as a prefix. * The default implementation will return user IP, user ID and session ID as a prefix.
* @param array $message the message being exported * @param array $message the message being exported.
* The message structure follows that in [[Logger::messages]].
* @return string the prefix string * @return string the prefix string
*/ */
public function getMessagePrefix($message) public function getMessagePrefix($message)
{ {
if ($this->prefix !== null) {
return call_user_func($this->prefix, $message);
}
$request = Yii::$app->getRequest(); $request = Yii::$app->getRequest();
$ip = $request instanceof Request ? $request->getUserIP() : '-'; $ip = $request instanceof Request ? $request->getUserIP() : '-';
/** @var \yii\web\User $user */ /** @var \yii\web\User $user */
$user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null; $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
$userID = $user ? $user->getId(false) : '-'; $userID = $user ? $user->getId(false) : '-';
/** @var \yii\web\Session $session */ /** @var \yii\web\Session $session */
$session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null; $session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null;
$sessionID = $session && $session->getIsActive() ? $session->getId() : '-'; $sessionID = $session && $session->getIsActive() ? $session->getId() : '-';
return "[$ip][$userID][$sessionID]"; return "[$ip][$userID][$sessionID]";
} }
} }
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