diff --git a/docs/guide/runtime-logging.md b/docs/guide/runtime-logging.md index c34daaf..6e00bb8 100644 --- a/docs/guide/runtime-logging.md +++ b/docs/guide/runtime-logging.md @@ -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 diff --git a/extensions/debug/panels/LogPanel.php b/extensions/debug/panels/LogPanel.php index af92474..35b07ce 100644 --- a/extensions/debug/panels/LogPanel.php +++ b/extensions/debug/panels/LogPanel.php @@ -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]; } diff --git a/extensions/debug/panels/ProfilingPanel.php b/extensions/debug/panels/ProfilingPanel.php index 635f4f8..9f08fdc 100644 --- a/extensions/debug/panels/ProfilingPanel.php +++ b/extensions/debug/panels/ProfilingPanel.php @@ -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, diff --git a/extensions/debug/views/default/panels/log/detail.php b/extensions/debug/views/default/panels/log/detail.php index aad9789..86f115d 100644 --- a/extensions/debug/views/default/panels/log/detail.php +++ b/extensions/debug/views/default/panels/log/detail.php @@ -1,6 +1,7 @@ <?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', diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 6733d2c..01c2167 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -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 ------------------------- diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index a437c01..615aae1 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -43,4 +43,7 @@ Upgrade from Yii 2.0 Beta of the callable to be `function ($model, $key, $index, $widget)`. The `$key` parameter was newly added in this release. -* `yii\console\controllers\AssetController` is now using hashes instead of timestamps. Replace all `{ts}` with `{hash}`. \ No newline at end of file +* `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;`. diff --git a/framework/helpers/BaseVarDumper.php b/framework/helpers/BaseVarDumper.php index 882fa4e..2ed84fa 100644 --- a/framework/helpers/BaseVarDumper.php +++ b/framework/helpers/BaseVarDumper.php @@ -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. diff --git a/framework/log/DbTarget.php b/framework/log/DbTarget.php index 52e1f23..86d0edd 100644 --- a/framework/log/DbTarget.php +++ b/framework/log/DbTarget.php @@ -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(); } } diff --git a/framework/log/Dispatcher.php b/framework/log/Dispatcher.php index a71c8cd..eda796d 100644 --- a/framework/log/Dispatcher.php +++ b/framework/log/Dispatcher.php @@ -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']); diff --git a/framework/log/EmailTarget.php b/framework/log/EmailTarget.php index 2564068..7259a2a 100644 --- a/framework/log/EmailTarget.php +++ b/framework/log/EmailTarget.php @@ -35,6 +35,7 @@ class EmailTarget extends Target */ public $mail = 'mail'; + /** * @inheritdoc */ diff --git a/framework/log/FileTarget.php b/framework/log/FileTarget.php index c1bf807..06edb42 100644 --- a/framework/log/FileTarget.php +++ b/framework/log/FileTarget.php @@ -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. diff --git a/framework/log/Logger.php b/framework/log/Logger.php index 029f474..540f79f 100644 --- a/framework/log/Logger.php +++ b/framework/log/Logger.php @@ -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`. diff --git a/framework/log/SyslogTarget.php b/framework/log/SyslogTarget.php index 8494eeb..c6ba3e2 100644 --- a/framework/log/SyslogTarget.php +++ b/framework/log/SyslogTarget.php @@ -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"; } } diff --git a/framework/log/Target.php b/framework/log/Target.php index 950852f..4b27ddc 100644 --- a/framework/log/Target.php +++ b/framework/log/Target.php @@ -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]"; } }