Commit a967dbe9 by Carsten Brandt

Merge pull request #3755 from yiisoft/logger-arrays

refactored logger to support array values out of the box.
parents a836ec17 9679721e
......@@ -15,6 +15,9 @@ Basic logging is as simple as calling one method:
\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
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
{
$target = $this->module->logTarget;
$messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE);
return ['messages' => $messages];
}
......
......@@ -69,7 +69,6 @@ class ProfilingPanel extends Panel
{
$target = $this->module->logTarget;
$messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE);
return [
'memory' => memory_get_peak_usage(),
'time' => microtime(true) - YII_BEGIN_TIME,
......
<?php
use yii\helpers\Html;
use yii\grid\GridView;
use yii\helpers\VarDumper;
use yii\log\Logger;
?>
......@@ -51,8 +52,7 @@ echo GridView::widget([
[
'attribute' => 'message',
'value' => function ($data) {
$message = nl2br(Html::encode($data['message']));
$message = Html::encode(is_string($data['message']) ? $data['message'] : VarDumper::export($data['message']));
if (!empty($data['trace'])) {
$message .= Html::ul($data['trace'], [
'class' => 'trace',
......@@ -61,7 +61,6 @@ echo GridView::widget([
}
]);
};
return $message;
},
'format' => 'html',
......
......@@ -60,6 +60,7 @@ Yii Framework 2 Change Log
- Enh #3222: Added `useTablePrefix` option to the model generator for Gii (horizons2)
- Enh #3230: Added `yii\filters\AccessControl::user` to support access control with different actors (qiangxue)
- Enh #3232: Added `export()` and `exportAsString()` methods to `yii\helpers\BaseVarDumper` (klimov-paul)
- Enh #3244: Allow logging complex data such as arrays and object via the log system (cebe)
- Enh #3252: Added support for case insensitive matching using ILIKE to PostgreSQL QueryBuilder (cebe)
- Enh #3284: Added support for checking multiple ETags by `yii\filters\HttpCache` (qiangxue)
- Enh #3298: Supported configuring `View::theme` using a class name (netyum, qiangxue)
......@@ -106,6 +107,8 @@ Yii Framework 2 Change Log
- Chg: `yii\data\ActiveDataProvider::$query` will not be modified directly with pagination and sorting anymore so it will be reuseable (cebe)
- Chg: Removed `yii\rest\ActiveController::$transactional` property and connected functionality (samdark)
- Chg: Changed the default value of the `keyPrefix` property of cache components to be null (qiangxue)
- Chg: Added `prefix` column to `yii\log\DbTarget` to have the same amount of information logged as in files and emails (cebe)
2.0.0-beta April 13, 2014
-------------------------
......
......@@ -44,3 +44,6 @@ Upgrade from Yii 2.0 Beta
in this release.
* `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
* This method is similar to `var_export()`. The main difference is that
* 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.
*
* @param mixed $var the variable to be exported.
......
......@@ -16,7 +16,7 @@ use yii\di\Instance;
* DbTarget stores log messages in a database 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>
* @since 2.0
......@@ -39,6 +39,7 @@ class DbTarget extends Target
* level INTEGER,
* category VARCHAR(255),
* log_time INTEGER,
* prefix TEXT,
* message TEXT,
* INDEX idx_log_level (level),
* INDEX idx_log_category (category)
......@@ -55,6 +56,7 @@ class DbTarget extends Target
*/
public $logTable = '{{%log}}';
/**
* Initializes the DbTarget component.
* 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
public function export()
{
$tableName = $this->db->quoteTableName($this->logTable);
$sql = "INSERT INTO $tableName ([[level]], [[category]], [[log_time]], [[message]])
VALUES (:level, :category, :log_time, :message)";
$sql = "INSERT INTO $tableName ([[level]], [[category]], [[log_time]], [[prefix]], [[message]])
VALUES (:level, :category, :log_time, :prefix, :message)";
$command = $this->db->createCommand($sql);
foreach ($this->messages as $message) {
list($text, $level, $category, $timestamp) = $message;
if (!is_string($text)) {
$text = var_export($text, true);
}
$command->bindValues([
':level' => $message[1],
':category' => $message[2],
':log_time' => $message[3],
':message' => $message[0],
':level' => $level,
':category' => $category,
':log_time' => $timestamp,
':prefix' => $this->getMessagePrefix($message),
':message' => $text,
])->execute();
}
}
......
......@@ -13,14 +13,14 @@ use yii\base\Component;
/**
* 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]].
*
* 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:
*
* ~~~
* ```php
* [
* 'components' => [
* 'log' => [
......@@ -41,14 +41,13 @@ use yii\base\Component;
* ],
* ],
* ]
* ~~~
* ```
*
* Each log target can have a name and can be referenced via the [[targets]] property
* as follows:
* Each log target can have a name and can be referenced via the [[targets]] property as follows:
*
* ~~~
* ```php
* Yii::$app->log->targets['file']->enabled = false;
* ~~~
* ```
*
* @property integer $flushInterval How many messages should be logged before they are sent to targets. This
* method returns the value of [[Logger::flushInterval]].
......@@ -66,6 +65,7 @@ class Dispatcher extends Component
* or the configuration for creating the log target instance.
*/
public $targets = [];
/**
* @var Logger the logger.
*/
......@@ -77,6 +77,7 @@ class Dispatcher extends Component
*/
public function __construct($config = [])
{
// ensure logger gets set before any other config option
if (isset($config['logger'])) {
$this->setLogger($config['logger']);
unset($config['logger']);
......
......@@ -35,6 +35,7 @@ class EmailTarget extends Target
*/
public $mail = 'mail';
/**
* @inheritdoc
*/
......
......@@ -57,6 +57,7 @@ class FileTarget extends Target
*/
public $rotateByCopy = false;
/**
* Initializes the route.
* This method is invoked after the route is created by the route manager.
......
......@@ -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 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
* via the [[Yii]] class:
*
......@@ -25,7 +25,8 @@ use yii\base\Component;
* - [[Yii::endProfile()]]
*
* 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
* second element the total time spent in SQL execution. This property is read-only.
......@@ -122,7 +123,8 @@ class Logger extends Component
* Logs a message with the given type and category.
* If [[traceLevel]] is greater than 0, additional call stack information about
* 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:
* `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`,
* `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`.
......
......@@ -8,6 +8,7 @@
namespace yii\log;
use Yii;
use yii\helpers\VarDumper;
/**
* SyslogTarget writes log to syslog.
......@@ -39,6 +40,7 @@ class SyslogTarget extends Target
Logger::LEVEL_ERROR => LOG_ERR,
];
/**
* Writes log messages to syslog
*/
......@@ -59,11 +61,10 @@ class SyslogTarget extends Target
list($text, $level, $category, $timestamp) = $message;
$level = Logger::getLevelName($level);
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";
}
}
......@@ -60,9 +60,12 @@ abstract class Target extends Component
*/
public $logVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'];
/**
* @var callable a PHP callable that returns a string to be prefix 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)`.
* @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 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;
/**
......@@ -79,6 +82,7 @@ abstract class Target extends Component
private $_levels = 0;
/**
* Exports log [[messages]] to a specific destination.
* Child classes must implement this method.
......@@ -177,7 +181,8 @@ abstract class Target extends Component
/**
* 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
* 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.
......@@ -214,14 +219,13 @@ abstract class Target extends Component
unset($messages[$i]);
}
}
return $messages;
}
/**
* Formats a log message.
* The message structure follows that in [[Logger::messages]].
* Formats a log message for display as a string.
* @param array $message the log message to be formatted.
* The message structure follows that in [[Logger::messages]].
* @return string the formatted message
*/
public function formatMessage($message)
......@@ -229,30 +233,38 @@ abstract class Target extends Component
list($text, $level, $category, $timestamp) = $message;
$level = Logger::getLevelName($level);
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";
}
/**
* 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.
* @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
*/
public function getMessagePrefix($message)
{
if ($this->prefix !== null) {
return call_user_func($this->prefix, $message);
}
$request = Yii::$app->getRequest();
$ip = $request instanceof Request ? $request->getUserIP() : '-';
/** @var \yii\web\User $user */
$user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
$userID = $user ? $user->getId(false) : '-';
/** @var \yii\web\Session $session */
$session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null;
$sessionID = $session && $session->getIsActive() ? $session->getId() : '-';
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