Browse Source

Fix #13702 PSR Logger (#14611)

Refactor `Logger` to be PSR compatible extracting Profiler
tags/3.0.0-alpha1
Paul Klimov 7 years ago committed by GitHub
parent
commit
3e6f8b1c19
  1. 2
      docs/guide/README.md
  2. 4
      docs/guide/concept-events.md
  3. 177
      docs/guide/runtime-logging.md
  4. 77
      docs/guide/runtime-profiling.md
  5. 2
      docs/guide/structure-filters.md
  6. 14
      docs/guide/tutorial-performance-tuning.md
  7. 114
      framework/BaseYii.php
  8. 11
      framework/UPGRADE.md
  9. 2
      framework/base/Action.php
  10. 64
      framework/base/Application.php
  11. 2
      framework/base/Controller.php
  12. 21
      framework/base/ErrorHandler.php
  13. 2
      framework/base/Event.php
  14. 2
      framework/base/InlineAction.php
  15. 2
      framework/base/Model.php
  16. 2
      framework/base/Module.php
  17. 2
      framework/base/View.php
  18. 1
      framework/classes.php
  19. 1
      framework/composer.json
  20. 4
      framework/db/Command.php
  21. 2
      framework/db/Connection.php
  22. 14
      framework/db/Transaction.php
  23. 4
      framework/filters/PageCache.php
  24. 2
      framework/filters/RateLimiter.php
  25. 15
      framework/log/DbTarget.php
  26. 206
      framework/log/Dispatcher.php
  27. 379
      framework/log/Logger.php
  28. 91
      framework/log/LoggerTarget.php
  29. 44
      framework/log/SyslogTarget.php
  30. 113
      framework/log/Target.php
  31. 7
      framework/log/migrations/m141106_185632_log_init.php
  32. 89
      framework/profile/LogTarget.php
  33. 216
      framework/profile/Profiler.php
  34. 54
      framework/profile/ProfilerInterface.php
  35. 106
      framework/profile/Target.php
  36. 4
      framework/rbac/DbManager.php
  37. 2
      framework/rbac/PhpManager.php
  38. 2
      framework/rest/UrlRule.php
  39. 2
      framework/web/Application.php
  40. 2
      framework/web/AssetConverter.php
  41. 2
      framework/web/CompositeUrlRule.php
  42. 6
      framework/web/UrlManager.php
  43. 2
      framework/web/UrlRule.php
  44. 134
      tests/framework/BaseYiiTest.php
  45. 26
      tests/framework/base/ApplicationTest.php
  46. 46
      tests/framework/db/ConnectionTest.php
  47. 2
      tests/framework/i18n/I18NTest.php
  48. 54
      tests/framework/log/DbTargetTest.php
  49. 266
      tests/framework/log/DispatcherTest.php
  50. 2
      tests/framework/log/EmailTargetTest.php
  51. 19
      tests/framework/log/FileTargetTest.php
  52. 175
      tests/framework/log/LoggerDispatchingTest.php
  53. 465
      tests/framework/log/LoggerTest.php
  54. 51
      tests/framework/log/SyslogTargetTest.php
  55. 76
      tests/framework/log/TargetTest.php
  56. 69
      tests/framework/profile/LogTargetTest.php
  57. 122
      tests/framework/profile/ProfilerTest.php
  58. 97
      tests/framework/profile/TargetTest.php

2
docs/guide/README.md

@ -55,7 +55,7 @@ Handling Requests
* [Sessions and Cookies](runtime-sessions-cookies.md)
* [Handling Errors](runtime-handling-errors.md)
* [Logging](runtime-logging.md)
* [Performance Profiling](runtime-profiling.md)
Key Concepts
------------

4
docs/guide/concept-events.md

@ -212,7 +212,7 @@ use yii\base\Event;
use yii\db\ActiveRecord;
Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
Yii::trace(get_class($event->sender) . ' is inserted');
Yii::debug(get_class($event->sender) . ' is inserted');
});
```
@ -296,7 +296,7 @@ pass the interface class name as the first argument:
```php
Event::on(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::trace(get_class($event->sender) . ' just danced'); // Will log that Dog or Developer danced
Yii::debug(get_class($event->sender) . ' just danced'); // Will log that Dog or Developer danced
});
```

177
docs/guide/runtime-logging.md

@ -18,7 +18,7 @@ In this section, we will mainly describe the first two steps.
Recording log messages is as simple as calling one of the following logging methods:
* [[Yii::trace()]]: record a message to trace how a piece of code runs. This is mainly for development use.
* [[Yii::debug()]]: record a message to debug how a piece of code runs. This is mainly for development use.
* [[Yii::info()]]: record a message that conveys some useful information.
* [[Yii::warning()]]: record a warning message that indicates something unexpected has happened.
* [[Yii::error()]]: record a fatal error that should be investigated as soon as possible.
@ -26,10 +26,10 @@ Recording log messages is as simple as calling one of the following logging meth
These logging methods record log messages at various *severity levels* and *categories*. They share
the same function signature `function ($message, $category = 'application')`, where `$message` stands for
the log message to be recorded, while `$category` is the category of the log message. The code in the following
example records a trace message under the default category `application`:
example records a debug message under the default category `application`:
```php
Yii::trace('start calculating average revenue');
Yii::debug('start calculating average revenue');
```
> Info: Log messages can be strings as well as complex data, such as arrays or objects. It is the responsibility
@ -43,17 +43,15 @@ is to use the PHP magic constant `__METHOD__` for the category names. This is al
Yii framework code. For example,
```php
Yii::trace('start calculating average revenue', __METHOD__);
Yii::debug('start calculating average revenue', __METHOD__);
```
The `__METHOD__` constant evaluates as the name of the method (prefixed with the fully qualified class name) where
the constant appears. For example, it is equal to the string `'app\controllers\RevenueController::calculate'` if
the above line of code is called within this method.
> Info: The logging methods described above are actually shortcuts to the [[yii\log\Logger::log()|log()]] method
of the [[yii\log\Logger|logger object]] which is a singleton accessible through the expression `Yii::getLogger()`. When
enough messages are logged or when the application ends, the logger object will call a
[[yii\log\Dispatcher|message dispatcher]] to send recorded log messages to the registered [log targets](#log-targets).
> Info: The logging methods described above are actually shortcuts to the methods of [[yii\log\Logger|logger object]] which is a singleton accessible through the expression `Yii::getLogger()`. When
enough messages are logged or when the application ends, the logger object will send recorded log messages to the registered [log targets](#log-targets).
## Log Targets <span id="log-targets"></span>
@ -63,41 +61,33 @@ severity levels and categories and then exports them to some medium. For example
exports the filtered log messages to a database table, while an [[yii\log\EmailTarget|email target]] exports
the log messages to specified email addresses.
You can register multiple log targets in an application by configuring them through the `log` [application component](structure-application-components.md)
You can register multiple log targets in an application by configuring them through the [[yii\base\Application::$logger|logger application property]]
in the application configuration, like the following:
```php
return [
// the "log" component must be loaded during bootstrapping time
'bootstrap' => ['log'],
'components' => [
'log' => [
'targets' => [
[
'class' => 'yii\log\DbTarget',
'levels' => ['error', 'warning'],
],
[
'class' => 'yii\log\EmailTarget',
'levels' => ['error'],
'categories' => ['yii\db\*'],
'message' => [
'from' => ['log@example.com'],
'to' => ['admin@example.com', 'developer@example.com'],
'subject' => 'Database errors at example.com',
],
'logger' => [
'targets' => [
[
'class' => \yii\log\DbTarget::class,
'levels' => ['error', 'warning'],
],
[
'class' => \yii\log\EmailTarget::class,
'levels' => ['error'],
'categories' => ['yii\db\*'],
'message' => [
'from' => ['log@example.com'],
'to' => ['admin@example.com', 'developer@example.com'],
'subject' => 'Database errors at example.com',
],
],
],
],
],
];
```
> Note: The `log` component must be loaded during [bootstrapping](runtime-bootstrapping.md) time so that
it can dispatch log messages to targets promptly. That is why it is listed in the `bootstrap` array as shown above.
In the above code, two log targets are registered in the [[yii\log\Dispatcher::targets]] property:
In the above code, two log targets are registered:
* the first target selects error and warning messages and saves them in a database table;
* the second target selects error messages under the categories whose names start with `yii\db\`, and sends
@ -124,9 +114,7 @@ The [[yii\log\Target::levels|levels]] property takes an array consisting of one
* `error`: corresponding to messages logged by [[Yii::error()]].
* `warning`: corresponding to messages logged by [[Yii::warning()]].
* `info`: corresponding to messages logged by [[Yii::info()]].
* `trace`: corresponding to messages logged by [[Yii::trace()]].
* `profile`: corresponding to messages logged by [[Yii::beginProfile()]] and [[Yii::endProfile()]], which will
be explained in more details in the [Profiling](#performance-profiling) subsection.
* `debug`: corresponding to messages logged by [[Yii::debug()]].
If you do not specify the [[yii\log\Target::levels|levels]] property, it means the target will process messages
of *any* severity level.
@ -150,7 +138,7 @@ under the categories whose names match either `yii\db\*` or `yii\web\HttpExcepti
```php
[
'class' => 'yii\log\FileTarget',
'class' => \yii\log\FileTarget::class,
'levels' => ['error', 'warning'],
'categories' => [
'yii\db\*',
@ -174,7 +162,7 @@ a log target of the class [[yii\log\FileTarget]], you may find a log message sim
`runtime/log/app.log` file:
```
2014-10-04 18:10:15 [::1][][-][trace][yii\base\Module::getModule] Loading module: debug
2014-10-04 18:10:15 [::1][][-][debug][yii\base\Module::getModule] Loading module: debug
```
By default, log messages will be formatted as follows by the [[yii\log\Target::formatMessage()]]:
@ -189,7 +177,7 @@ log message with the current user ID (IP address and Session ID are removed for
```php
[
'class' => 'yii\log\FileTarget',
'class' => \yii\log\FileTarget::class,
'prefix' => function ($message) {
$user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
$userID = $user ? $user->getId(false) : '-';
@ -206,7 +194,7 @@ log target configuration specifies that only the value of the `$_SERVER` variabl
```php
[
'class' => 'yii\log\FileTarget',
'class' => \yii\log\FileTarget::class,
'logVars' => ['_SERVER'],
]
```
@ -219,21 +207,18 @@ Or if you want to implement your own way of providing context information, you m
### Message Trace Level <span id="trace-level"></span>
During development, it is often desirable to see where each log message is coming from. This can be achieved by
configuring the [[yii\log\Dispatcher::traceLevel|traceLevel]] property of the `log` component like the following:
configuring the [[yii\log\Logger::traceLevel|traceLevel]] property of the application `logger` like the following:
```php
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [...],
],
'logger' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [...],
],
];
```
The above application configuration sets [[yii\log\Dispatcher::traceLevel|traceLevel]] to be 3 if `YII_DEBUG` is on
The above application configuration sets [[yii\log\Logger::traceLevel|traceLevel]] to be 3 if `YII_DEBUG` is on
and 0 if `YII_DEBUG` is off. This means, if `YII_DEBUG` is on, each log message will be appended with at most 3
levels of the call stack at which the log message is recorded; and if `YII_DEBUG` is off, no call stack information
will be included.
@ -247,17 +232,14 @@ or when debugging an application.
As aforementioned, log messages are maintained in an array by the [[yii\log\Logger|logger object]]. To limit the
memory consumption by this array, the logger will flush the recorded messages to the [log targets](#log-targets)
each time the array accumulates a certain number of log messages. You can customize this number by configuring
the [[yii\log\Dispatcher::flushInterval|flushInterval]] property of the `log` component:
the [[yii\log\Logger::flushInterval|flushInterval]] property of the application `logger`:
```php
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'flushInterval' => 100, // default is 1000
'targets' => [...],
],
'logger' => [
'flushInterval' => 100, // default is 1000
'targets' => [...],
],
];
```
@ -271,28 +253,25 @@ property of individual [log targets](#log-targets), like the following,
```php
[
'class' => 'yii\log\FileTarget',
'class' => \yii\log\FileTarget::class,
'exportInterval' => 100, // default is 1000
]
```
Because of the flushing and exporting level setting, by default when you call `Yii::trace()` or any other logging
Because of the flushing and exporting level setting, by default when you call `Yii::debug()` or any other logging
method, you will NOT see the log message immediately in the log targets. This could be a problem for some long-running
console applications. To make each log message appear immediately in the log targets, you should set both
[[yii\log\Dispatcher::flushInterval|flushInterval]] and [[yii\log\Target::exportInterval|exportInterval]] to be 1,
[[yii\log\Logger::flushInterval|flushInterval]] and [[yii\log\Target::exportInterval|exportInterval]] to be 1,
as shown below:
```php
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'flushInterval' => 1,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'exportInterval' => 1,
],
'logger' => [
'flushInterval' => 1,
'targets' => [
[
'class' => \yii\log\FileTarget::class,
'exportInterval' => 1,
],
],
],
@ -308,7 +287,7 @@ You can enable or disable a log target by configuring its [[yii\log\Target::enab
You may do so via the log target configuration or by the following PHP statement in your code:
```php
Yii::$app->log->targets['file']->enabled = false;
Yii::$app->logger->targets['file']->enabled = false;
```
The above code requires you to name a target as `file`, as shown below by using string keys in the
@ -316,16 +295,13 @@ The above code requires you to name a target as `file`, as shown below by using
```php
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'targets' => [
'file' => [
'class' => 'yii\log\FileTarget',
],
'db' => [
'class' => 'yii\log\DbTarget',
],
'logger' => [
'targets' => [
'file' => [
'class' => \yii\log\FileTarget::class,
],
'db' => [
'class' => \yii\log\DbTarget::class,
],
],
],
@ -340,48 +316,5 @@ sending the content of the [[yii\log\Target::messages]] array to a designated me
[[yii\log\Target::formatMessage()]] method to format each message. For more details, you may refer to any of the
log target classes included in the Yii release.
> Tip: Instead of creating your own loggers you may try any PSR-3 compatible logger such
as [Monolog](https://github.com/Seldaek/monolog) by using
[PSR log target extension](https://github.com/samdark/yii2-psr-log-target).
## Performance Profiling <span id="performance-profiling"></span>
Performance profiling is a special type of message logging that is used to measure the time taken by certain
code blocks and find out what are the performance bottlenecks. For example, the [[yii\db\Command]] class uses
performance profiling to find out the time taken by each DB query.
To use performance profiling, first identify the code blocks that need to be profiled. Then enclose each
code block like the following:
```php
\Yii::beginProfile('myBenchmark');
...code block being profiled...
\Yii::endProfile('myBenchmark');
```
where `myBenchmark` stands for a unique token identifying a code block. Later when you examine the profiling
result, you will use this token to locate the time spent by the corresponding code block.
It is important to make sure that the pairs of `beginProfile` and `endProfile` are properly nested.
For example,
```php
\Yii::beginProfile('block1');
// some code to be profiled
\Yii::beginProfile('block2');
// some other code to be profiled
\Yii::endProfile('block2');
\Yii::endProfile('block1');
```
If you miss `\Yii::endProfile('block1')` or switch the order of `\Yii::endProfile('block1')` and
`\Yii::endProfile('block2')`, the performance profiling will not work.
> Tip: Instead of creating your own loggers you may try using PSR-3 compatible targets.
For each code block being profiled, a log message with the severity level `profile` is recorded. You can configure
a [log target](#log-targets) to collect such messages and export them. The [Yii debugger](tool-debugger.md) has
a built-in performance profiling panel showing the profiling results.

77
docs/guide/runtime-profiling.md

@ -0,0 +1,77 @@
# Performance Profiling
You should profile your code to find out the performance bottlenecks and take appropriate measures accordingly.
Profiling measures the time and memory taken by certain code blocks to execute.
To use it, first identify the code blocks that need to be profiled. Then enclose each
code block like the following:
```php
\Yii::beginProfile('myBenchmark');
...code block being profiled...
\Yii::endProfile('myBenchmark');
```
where `myBenchmark` stands for a unique token identifying a code block. Later when you examine the profiling
result, you will use this token to locate the time spent by the corresponding code block.
It is important to make sure that the pairs of `beginProfile` and `endProfile` are properly nested.
For example,
```php
\Yii::beginProfile('block1');
// some code to be profiled
\Yii::beginProfile('block2');
// some other code to be profiled
\Yii::endProfile('block2');
\Yii::endProfile('block1');
```
If you miss `\Yii::endProfile('block1')` or switch the order of `\Yii::endProfile('block1')` and
`\Yii::endProfile('block2')`, the performance profiling will not work.
Profiling results could be either reviewed in the
[Yii debugger](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) built-in performance profiling panel or sent to
targets configured.
## Configuring profiler targets
Additionally to using Yii debugger, you may want to send profiling messages to log or other targets. In order to do that you need to
configure profiling targets via application config:
```php
return [
'profiler' => [
'targets => [
'class' => /yii/profile/LogTarget::class,
],
],
];
```
After profiler `LogTarget` is configured, for each code block being profiled, a log message with the severity level `debug` is recorded.
## Extra tools
In case you need to profile basically every call it's a good idea to use lower level tools.
### XDebug Profiler
If you have XDebug installed, it has
[built-in profiler](http://xdebug.org/docs/profiler). Note, hovever, that enabling XDebug slows down the application significantly so
both profiling results may be inaccurate and it's not applicable to production environments.
### XHProf
[XHProf](http://www.php.net/manual/en/book.xhprof.php) is an open solution in that is meant to be executed in both development and
production environments. It does not affect performance significantly. Several GUIs exist to analyze its results.
### Blackfire
[Blackfire](https://blackfire.io/) is a commerial PHP profiler. Same as XHProf it does not affect performance. UI to analyze data is
provided as SAAS solution.

2
docs/guide/structure-filters.md

@ -90,7 +90,7 @@ class ActionTimeFilter extends ActionFilter
public function afterAction($action, $result)
{
$time = microtime(true) - $this->_startTime;
Yii::trace("Action '{$action->uniqueId}' spent $time second.");
Yii::debug("Action '{$action->uniqueId}' spent $time second.");
return parent::afterAction($action, $result);
}
}

14
docs/guide/tutorial-performance-tuning.md

@ -209,16 +209,10 @@ In the push method, you would use a message queue (e.g. RabbitMQ, ActiveMQ, Amaz
Whenever a new task is put on the queue, it will initiate or notify the task handling process to trigger the task processing.
## Performance Profiling <span id="performance-profiling"></span>
You should profile your code to find out the performance bottlenecks and take appropriate measures accordingly.
The following profiling tools may be useful:
- [Yii debug toolbar and debugger](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md)
- [Blackfire](https://blackfire.io/)
- [XHProf](http://www.php.net/manual/en/book.xhprof.php)
- [XDebug profiler](http://xdebug.org/docs/profiler)
## Prepare application for scaling
When nothing helps you may try making your application scalabe. A good introduction is provided in [Configuring a Yii 2 Application for an Autoscaling Stack](https://github.com/samdark/yii2-cookbook/blob/master/book/scaling.md). For further reading you may refer to [Web apps performance and scaling](http://thehighload.com/).
## Additional reading
- Guide on [Performance Profiling](runtime-profiling.md)

114
framework/BaseYii.php

@ -7,11 +7,17 @@
namespace yii;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use yii\base\InvalidConfigException;
use yii\base\InvalidArgumentException;
use yii\base\UnknownClassException;
use yii\di\Container;
use yii\di\Instance;
use yii\helpers\VarDumper;
use yii\log\Logger;
use yii\profile\Profiler;
use yii\profile\ProfilerInterface;
/**
* Gets the application start timestamp.
@ -355,10 +361,13 @@ class BaseYii
throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
}
/**
* @var LoggerInterface logger instance.
*/
private static $_logger;
/**
* @return Logger message logger
* @return LoggerInterface message logger
*/
public static function getLogger()
{
@ -366,30 +375,111 @@ class BaseYii
return self::$_logger;
}
return self::$_logger = static::createObject('yii\log\Logger');
return self::$_logger = Instance::ensure(['class' => Logger::class], LoggerInterface::class);
}
/**
* Sets the logger object.
* @param Logger $logger the logger object.
* @param LoggerInterface|\Closure|array|null $logger the logger object or its DI compatible configuration.
*/
public static function setLogger($logger)
{
self::$_logger = $logger;
if ($logger === null) {
self::$_logger = null;
return;
}
if (is_array($logger)) {
if (!isset($logger['class']) && is_object(self::$_logger)) {
static::configure(self::$_logger, $logger);
return;
}
$logger = array_merge(['class' => Logger::class], $logger);
} elseif ($logger instanceof \Closure) {
$logger = call_user_func($logger);
}
self::$_logger = Instance::ensure($logger, LoggerInterface::class);
}
/**
* @var ProfilerInterface profiler instance.
* @since 2.1
*/
private static $_profiler;
/**
* @return ProfilerInterface profiler instance.
* @since 2.1
*/
public static function getProfiler()
{
if (self::$_profiler !== null) {
return self::$_profiler;
}
return self::$_profiler = Instance::ensure(['class' => Profiler::class], ProfilerInterface::class);
}
/**
* @param ProfilerInterface|\Closure|array|null $profiler profiler instance or its DI compatible configuration.
* @since 2.1
*/
public static function setProfiler($profiler)
{
if ($profiler === null) {
self::$_profiler = null;
return;
}
if (is_array($profiler)) {
if (!isset($profiler['class']) && is_object(self::$_profiler)) {
static::configure(self::$_profiler, $profiler);
return;
}
$profiler = array_merge(['class' => Profiler::class], $profiler);
} elseif ($profiler instanceof \Closure) {
$profiler = call_user_func($profiler);
}
self::$_profiler = Instance::ensure($profiler, ProfilerInterface::class);
}
/**
* Logs a message with category.
* @param string $level log level.
* @param mixed $message the message to be logged. This can be a simple string or a more
* complex data structure, such as array.
* @param string $category the category of the message.
* @since 2.1.0
*/
public static function log($level, $message, $category = 'application')
{
$context = ['category' => $category];
if (!is_string($message)) {
if ($message instanceof \Throwable) {
// exceptions are string-convertable, thus should be passed as it is to the logger
// if exception instance is given to produce a stack trace, it MUST be in a key named "exception".
$context['exception'] = $message;
} else {
// exceptions may not be serializable if in the call stack somewhere is a Closure
$message = VarDumper::export($message);
}
}
static::getLogger()->log($level, $message, $context);
}
/**
* Logs a trace message.
* Logs a debug message.
* Trace messages are logged mainly for development purpose to see
* the execution work flow of some code.
* @param string|array $message the message to be logged. This can be a simple string or a more
* complex data structure, such as array.
* @param string $category the category of the message.
*/
public static function trace($message, $category = 'application')
public static function debug($message, $category = 'application')
{
if (YII_DEBUG) {
static::getLogger()->log($message, Logger::LEVEL_TRACE, $category);
static::log(LogLevel::DEBUG, $message, $category);
}
}
@ -403,7 +493,7 @@ class BaseYii
*/
public static function error($message, $category = 'application')
{
static::getLogger()->log($message, Logger::LEVEL_ERROR, $category);
static::log(LogLevel::ERROR, $message, $category);
}
/**
@ -416,7 +506,7 @@ class BaseYii
*/
public static function warning($message, $category = 'application')
{
static::getLogger()->log($message, Logger::LEVEL_WARNING, $category);
static::log(LogLevel::WARNING, $message, $category);
}
/**
@ -429,7 +519,7 @@ class BaseYii
*/
public static function info($message, $category = 'application')
{
static::getLogger()->log($message, Logger::LEVEL_INFO, $category);
static::log(LogLevel::INFO, $message, $category);
}
/**
@ -451,7 +541,7 @@ class BaseYii
*/
public static function beginProfile($token, $category = 'application')
{
static::getLogger()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category);
static::getProfiler()->begin($token, ['category' => $category]);
}
/**
@ -463,7 +553,7 @@ class BaseYii
*/
public static function endProfile($token, $category = 'application')
{
static::getLogger()->log($token, Logger::LEVEL_PROFILE_END, $category);
static::getProfiler()->end($token, ['category' => $category]);
}
/**

11
framework/UPGRADE.md

@ -69,6 +69,17 @@ Upgrade from Yii 2.0.x
Mail view rendering is now encapsulated into `yii\mail\Template` class.
* Properties `view`, `viewPath`, `htmlLayout` and `textLayout` have been moved from `yii\mail\BaseMailer` to `yii\mail\Composer` class,
which now encapsulates message composition.
* Interface of `yii\log\Logger` has been changed according to PSR-3 `Psr\Log\LoggerInterface`.
Make sure you update your code accordingly in case you invoke `Logger` methods directly.
* Constants `yii\log\Logger::LEVEL_ERROR`, `yii\log\Logger::LEVEL_WARNING` and so on have been removed.
Use constants from `Psr\Log\LogLevel` instead.
* Method `yii\BaseYii::trace()` has been renamed to `debug()`. Make sure you use correct name for it.
* Class `yii\log\Dispatcher` has been removed as well as application 'log' component. Log targets
now should be configured using `yii\base\Application::$logger` property. Neither 'log' or 'logger'
components should be present at `yii\base\Application::$bootstrap`
* Profiling related functionality has been extracted into a separated component under `yii\profile\ProfilerInterface`.
Profiling messages should be collection using `yii\base\Application::$profiler`. In case you wish to
continue storing profiling messages along with the log ones, you may use `yii\profile\LogTarget` profiling target.
* `yii\captcha\CaptchaAction` has been refactored. Rendering logic was extracted into `yii\captcha\DriverInterface`, which
instance is available via `yii\captcha\CaptchaAction::$driver` field. All image settings now should be passed to
the driver fields instead of action. Automatic detection of the rendering driver is no longer supported.

2
framework/base/Action.php

@ -86,7 +86,7 @@ class Action extends Component
throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
}
$args = $this->controller->bindActionParams($this, $params);
Yii::trace('Running action: ' . get_class($this) . '::run()', __METHOD__);
Yii::debug('Running action: ' . get_class($this) . '::run()', __METHOD__);
if (Yii::$app->requestedParams === null) {
Yii::$app->requestedParams = $args;
}

64
framework/base/Application.php

@ -27,7 +27,8 @@ use Yii;
* component. This property is read-only.
* @property \yii\i18n\Formatter $formatter The formatter application component. This property is read-only.
* @property \yii\i18n\I18N $i18n The internationalization application component. This property is read-only.
* @property \yii\log\Dispatcher $log The log dispatcher application component. This property is read-only.
* @property \psr\log\LoggerInterface $logger The logger.
* @property \yii\profile\ProfilerInterface $profiler The profiler.
* @property \yii\mail\MailerInterface $mailer The mailer application component. This property is read-only.
* @property \yii\web\Request|\yii\console\Request $request The request component. This property is read-only.
* @property \yii\web\Response|\yii\console\Response $response The response component. This property is
@ -250,10 +251,19 @@ abstract class Application extends Module
if (isset($config['container'])) {
$this->setContainer($config['container']);
unset($config['container']);
}
if (isset($config['logger'])) {
$this->setLogger($config['logger']);
unset($config['logger']);
}
if (isset($config['profiler'])) {
$this->setProfiler($config['profiler']);
unset($config['profiler']);
}
// merge core components with custom components
foreach ($this->coreComponents() as $id => $component) {
if (!isset($config['components'][$id])) {
@ -293,10 +303,10 @@ abstract class Application extends Module
if (isset($extension['bootstrap'])) {
$component = Yii::createObject($extension['bootstrap']);
if ($component instanceof BootstrapInterface) {
Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
Yii::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
$component->bootstrap($this);
} else {
Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
}
}
}
@ -304,7 +314,7 @@ abstract class Application extends Module
foreach ($this->bootstrap as $mixed) {
$component = null;
if ($mixed instanceof \Closure) {
Yii::trace('Bootstrap with Closure', __METHOD__);
Yii::debug('Bootstrap with Closure', __METHOD__);
if (!$component = call_user_func($mixed, $this)) {
continue;
}
@ -323,10 +333,10 @@ abstract class Application extends Module
}
if ($component instanceof BootstrapInterface) {
Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
Yii::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
$component->bootstrap($this);
} else {
Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
}
}
}
@ -500,12 +510,43 @@ abstract class Application extends Module
}
/**
* Returns the log dispatcher component.
* @return \yii\log\Dispatcher the log dispatcher application component.
* Sets up or configure the logger instance.
* @param \psr\log\LoggerInterface|\Closure|array|null $logger the logger object or its DI compatible configuration.
* @since 2.1.0
*/
public function setLogger($logger)
{
Yii::setLogger($logger);
}
/**
* Returns the logger instance.
* @return \psr\log\LoggerInterface the logger instance.
* @since 2.1.0
*/
public function getLogger()
{
return Yii::getLogger();
}
/**
* Sets up or configure the profiler instance.
* @param \yii\profile\ProfilerInterface|\Closure|array|null $profiler the profiler object or its DI compatible configuration.
* @since 2.1.0
*/
public function setProfiler($profiler)
{
Yii::setProfiler($profiler);
}
/**
* Returns the profiler instance.
* @return \yii\profile\ProfilerInterface profiler instance.
* @since 2.1.0
*/
public function getLog()
public function getProfiler()
{
return $this->get('log');
return Yii::getProfiler();
}
/**
@ -627,7 +668,6 @@ abstract class Application extends Module
'security' => ['class' => Security::class],
'formatter' => ['class' => \yii\i18n\Formatter::class],
'i18n' => ['class' => \yii\i18n\I18N::class],
'log' => ['class' => \yii\log\Dispatcher::class],
'mailer' => ['class' => \yii\swiftmailer\Mailer::class],
'assetManager' => ['class' => \yii\web\AssetManager::class],
'urlManager' => ['class' => \yii\web\UrlManager::class],

2
framework/base/Controller.php

@ -127,7 +127,7 @@ class Controller extends Component implements ViewContextInterface
throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
}
Yii::trace('Route to run: ' . $action->getUniqueId(), __METHOD__);
Yii::debug('Route to run: ' . $action->getUniqueId(), __METHOD__);
if (Yii::$app->requestedAction === null) {
Yii::$app->requestedAction = $action;

21
framework/base/ErrorHandler.php

@ -103,7 +103,8 @@ abstract class ErrorHandler extends Component
}
$this->renderException($exception);
if (!YII_ENV_TEST) {
\Yii::getLogger()->flush(true);
Yii::getProfiler()->flush();
$this->flushLogger();
exit(1);
}
} catch (\Throwable $e) {
@ -205,7 +206,8 @@ abstract class ErrorHandler extends Component
$this->renderException($exception);
// need to explicitly flush logs because exit() next will terminate the app immediately
Yii::getLogger()->flush(true);
Yii::getProfiler()->flush();
$this->flushLogger();
exit(1);
}
}
@ -282,4 +284,19 @@ abstract class ErrorHandler extends Component
}
return $message;
}
/**
* Attempts to flush logger messages.
* @since 2.1
*/
protected function flushLogger()
{
$logger = Yii::getLogger();
if ($logger instanceof \yii\log\Logger) {
$logger->flush(true);
}
// attempt to invoke logger destructor:
unset($logger);
Yii::setLogger(null);
}
}

2
framework/base/Event.php

@ -67,7 +67,7 @@ class Event extends BaseObject
*
* ```php
* Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
* Yii::trace(get_class($event->sender) . ' is inserted.');
* Yii::debug(get_class($event->sender) . ' is inserted.');
* });
* ```
*

2
framework/base/InlineAction.php

@ -49,7 +49,7 @@ class InlineAction extends Action
public function runWithParams($params)
{
$args = $this->controller->bindActionParams($this, $params);
Yii::trace('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__);
Yii::debug('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__);
if (Yii::$app->requestedParams === null) {
Yii::$app->requestedParams = $args;
}

2
framework/base/Model.php

@ -713,7 +713,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
public function onUnsafeAttribute($name, $value)
{
if (YII_DEBUG) {
Yii::trace("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
Yii::debug("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
}
}

2
framework/base/Module.php

@ -418,7 +418,7 @@ class Module extends ServiceLocator
if ($this->_modules[$id] instanceof self) {
return $this->_modules[$id];
} elseif ($load) {
Yii::trace("Loading module: $id", __METHOD__);
Yii::debug("Loading module: $id", __METHOD__);
/* @var $module Module */
$module = Yii::createObject($this->_modules[$id], [$id, $this]);
$module->setInstance($module);

2
framework/base/View.php

@ -237,7 +237,7 @@ class View extends Component
$this->_viewFiles[] = $viewFile;
if ($this->beforeRender($viewFile, $params)) {
Yii::trace("Rendering view file: $viewFile", __METHOD__);
Yii::debug("Rendering view file: $viewFile", __METHOD__);
$ext = pathinfo($viewFile, PATHINFO_EXTENSION);
if (isset($this->renderers[$ext])) {
if (is_array($this->renderers[$ext]) || is_string($this->renderers[$ext])) {

1
framework/classes.php

@ -202,7 +202,6 @@ return [
'yii\i18n\MissingTranslationEvent' => YII2_PATH . '/i18n/MissingTranslationEvent.php',
'yii\i18n\PhpMessageSource' => YII2_PATH . '/i18n/PhpMessageSource.php',
'yii\log\DbTarget' => YII2_PATH . '/log/DbTarget.php',
'yii\log\Dispatcher' => YII2_PATH . '/log/Dispatcher.php',
'yii\log\EmailTarget' => YII2_PATH . '/log/EmailTarget.php',
'yii\log\FileTarget' => YII2_PATH . '/log/FileTarget.php',
'yii\log\Logger' => YII2_PATH . '/log/Logger.php',

1
framework/composer.json

@ -67,6 +67,7 @@
"ext-mbstring": "*",
"ext-ctype": "*",
"lib-pcre": "*",
"psr/log": "~1.0.2",
"yiisoft/yii2-composer": "~2.0.4",
"psr/simple-cache": "~1.0.0",
"ezyang/htmlpurifier": "~4.6",

4
framework/db/Command.php

@ -1018,7 +1018,7 @@ class Command extends Component
];
$result = $cache->get($cacheKey);
if (is_array($result) && isset($result[0])) {
Yii::trace('Query result served from cache', 'yii\db\Command::query');
Yii::debug('Query result served from cache', 'yii\db\Command::query');
return $result[0];
}
}
@ -1049,7 +1049,7 @@ class Command extends Component
if (isset($cache, $cacheKey, $info)) {
$cache->set($cacheKey, [$result], $info[1], $info[2]);
Yii::trace('Saved query result in cache', 'yii\db\Command::query');
Yii::debug('Saved query result in cache', 'yii\db\Command::query');
}
return $result;

2
framework/db/Connection.php

@ -595,7 +595,7 @@ class Connection extends Component
}
if ($this->pdo !== null) {
Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__);
Yii::debug('Closing DB connection: ' . $this->dsn, __METHOD__);
$this->pdo = null;
$this->_schema = null;
$this->_transaction = null;

14
framework/db/Transaction.php

@ -115,7 +115,7 @@ class Transaction extends \yii\base\BaseObject
if ($isolationLevel !== null) {
$this->db->getSchema()->setTransactionIsolationLevel($isolationLevel);
}
Yii::trace('Begin transaction' . ($isolationLevel ? ' with isolation level ' . $isolationLevel : ''), __METHOD__);
Yii::debug('Begin transaction' . ($isolationLevel ? ' with isolation level ' . $isolationLevel : ''), __METHOD__);
$this->db->trigger(Connection::EVENT_BEGIN_TRANSACTION);
$this->db->pdo->beginTransaction();
@ -126,7 +126,7 @@ class Transaction extends \yii\base\BaseObject
$schema = $this->db->getSchema();
if ($schema->supportsSavepoint()) {
Yii::trace('Set savepoint ' . $this->_level, __METHOD__);
Yii::debug('Set savepoint ' . $this->_level, __METHOD__);
$schema->createSavepoint('LEVEL' . $this->_level);
} else {
Yii::info('Transaction not started: nested transaction not supported', __METHOD__);
@ -146,7 +146,7 @@ class Transaction extends \yii\base\BaseObject
$this->_level--;
if ($this->_level === 0) {
Yii::trace('Commit transaction', __METHOD__);
Yii::debug('Commit transaction', __METHOD__);
$this->db->pdo->commit();
$this->db->trigger(Connection::EVENT_COMMIT_TRANSACTION);
return;
@ -154,7 +154,7 @@ class Transaction extends \yii\base\BaseObject
$schema = $this->db->getSchema();
if ($schema->supportsSavepoint()) {
Yii::trace('Release savepoint ' . $this->_level, __METHOD__);
Yii::debug('Release savepoint ' . $this->_level, __METHOD__);
$schema->releaseSavepoint('LEVEL' . $this->_level);
} else {
Yii::info('Transaction not committed: nested transaction not supported', __METHOD__);
@ -175,7 +175,7 @@ class Transaction extends \yii\base\BaseObject
$this->_level--;
if ($this->_level === 0) {
Yii::trace('Roll back transaction', __METHOD__);
Yii::debug('Roll back transaction', __METHOD__);
$this->db->pdo->rollBack();
$this->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION);
return;
@ -183,7 +183,7 @@ class Transaction extends \yii\base\BaseObject
$schema = $this->db->getSchema();
if ($schema->supportsSavepoint()) {
Yii::trace('Roll back to savepoint ' . $this->_level, __METHOD__);
Yii::debug('Roll back to savepoint ' . $this->_level, __METHOD__);
$schema->rollBackSavepoint('LEVEL' . $this->_level);
} else {
Yii::info('Transaction not rolled back: nested transaction not supported', __METHOD__);
@ -209,7 +209,7 @@ class Transaction extends \yii\base\BaseObject
if (!$this->getIsActive()) {
throw new Exception('Failed to set isolation level: transaction was inactive.');
}
Yii::trace('Setting transaction isolation level to ' . $level, __METHOD__);
Yii::debug('Setting transaction isolation level to ' . $level, __METHOD__);
$this->db->getSchema()->setTransactionIsolationLevel($level);
}

4
framework/filters/PageCache.php

@ -169,12 +169,12 @@ class PageCache extends ActionFilter
ob_start();
ob_implicit_flush(false);
$response->on(Response::EVENT_AFTER_SEND, [$this, 'cacheResponse']);
Yii::trace('Valid page content is not found in the cache.', __METHOD__);
Yii::debug('Valid page content is not found in the cache.', __METHOD__);
return true;
}
$this->restoreResponse($response, $data);
Yii::trace('Valid page content is found in the cache.', __METHOD__);
Yii::debug('Valid page content is found in the cache.', __METHOD__);
return false;
}

2
framework/filters/RateLimiter.php

@ -85,7 +85,7 @@ class RateLimiter extends ActionFilter
}
if ($this->user instanceof RateLimitInterface) {
Yii::trace('Check rate limit', __METHOD__);
Yii::debug('Check rate limit', __METHOD__);
$this->checkRateLimit($this->user, $this->request, $this->response, $action);
} elseif ($this->user) {
Yii::info('Rate limit skipped: "user" does not implement RateLimitInterface.', __METHOD__);

15
framework/log/DbTarget.php

@ -11,7 +11,6 @@ use Yii;
use yii\base\InvalidConfigException;
use yii\db\Connection;
use yii\di\Instance;
use yii\helpers\VarDumper;
/**
* DbTarget stores log messages in a database table.
@ -71,19 +70,11 @@ class DbTarget extends Target
VALUES (:level, :category, :log_time, :prefix, :message)";
$command = $this->db->createCommand($sql);
foreach ($this->messages as $message) {
[$text, $level, $category, $timestamp] = $message;
if (!is_string($text)) {
// exceptions may not be serializable if in the call stack somewhere is a Closure
if ($text instanceof \Throwable || $text instanceof \Exception) {
$text = (string) $text;
} else {
$text = VarDumper::export($text);
}
}
[$level, $text, $context] = $message;
$command->bindValues([
':level' => $level,
':category' => $category,
':log_time' => $timestamp,
':category' => $context['category'],
':log_time' => $context['time'],
':prefix' => $this->getMessagePrefix($message),
':message' => $text,
])->execute();

206
framework/log/Dispatcher.php

@ -1,206 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\log;
use Yii;
use yii\base\Component;
use yii\base\ErrorHandler;
/**
* Dispatcher manages a set of [[Target|log targets]].
*
* Dispatcher implements the [[dispatch()]]-method that forwards the log messages from a [[Logger]] to
* the registered log [[targets]].
*
* 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' => [
* 'targets' => [
* 'file' => [
* 'class' => \yii\log\FileTarget::class,
* 'levels' => ['trace', 'info'],
* 'categories' => ['yii\*'],
* ],
* 'email' => [
* 'class' => \yii\log\EmailTarget::class,
* 'levels' => ['error', 'warning'],
* 'message' => [
* 'to' => 'admin@example.com',
* ],
* ],
* ],
* ],
* ],
* ]
* ```
*
* 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 int $flushInterval How many messages should be logged before they are sent to targets. This
* method returns the value of [[Logger::flushInterval]].
* @property Logger $logger The logger. If not set, [[\Yii::getLogger()]] will be used. Note that the type of
* this property differs in getter and setter. See [[getLogger()]] and [[setLogger()]] for details.
* @property int $traceLevel How many application call stacks should be logged together with each message.
* This method returns the value of [[Logger::traceLevel]]. Defaults to 0.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Dispatcher extends Component
{
/**
* @var array|Target[] the log targets. Each array element represents a single [[Target|log target]] instance
* or the configuration for creating the log target instance.
*/
public $targets = [];
/**
* @var Logger the logger.
*/
private $_logger;
/**
* @inheritdoc
*/
public function __construct($config = [])
{
// ensure logger gets set before any other config option
if (isset($config['logger'])) {
$this->setLogger($config['logger']);
unset($config['logger']);
}
// connect logger and dispatcher
$this->getLogger();
parent::__construct($config);
}
/**
* @inheritdoc
*/
public function init()
{
parent::init();
foreach ($this->targets as $name => $target) {
if (!$target instanceof Target) {
$this->targets[$name] = Yii::createObject($target);
}
}
}
/**
* Gets the connected logger.
* If not set, [[\Yii::getLogger()]] will be used.
* @property Logger the logger. If not set, [[\Yii::getLogger()]] will be used.
* @return Logger the logger.
*/
public function getLogger()
{
if ($this->_logger === null) {
$this->setLogger(Yii::getLogger());
}
return $this->_logger;
}
/**
* Sets the connected logger.
* @param Logger|string|array $value the logger to be used. This can either be a logger instance
* or a configuration that will be used to create one using [[Yii::createObject()]].
*/
public function setLogger($value)
{
if (is_string($value) || is_array($value)) {
$value = Yii::createObject($value);
}
$this->_logger = $value;
$this->_logger->dispatcher = $this;
}
/**
* @return int how many application call stacks should be logged together with each message.
* This method returns the value of [[Logger::traceLevel]]. Defaults to 0.
*/
public function getTraceLevel()
{
return $this->getLogger()->traceLevel;
}
/**
* @param int $value how many application call stacks should be logged together with each message.
* This method will set the value of [[Logger::traceLevel]]. If the value is greater than 0,
* at most that number of call stacks will be logged. Note that only application call stacks are counted.
* Defaults to 0.
*/
public function setTraceLevel($value)
{
$this->getLogger()->traceLevel = $value;
}
/**
* @return int how many messages should be logged before they are sent to targets.
* This method returns the value of [[Logger::flushInterval]].
*/
public function getFlushInterval()
{
return $this->getLogger()->flushInterval;
}
/**
* @param int $value how many messages should be logged before they are sent to targets.
* This method will set the value of [[Logger::flushInterval]].
* Defaults to 1000, meaning the [[Logger::flush()]] method will be invoked once every 1000 messages logged.
* Set this property to be 0 if you don't want to flush messages until the application terminates.
* This property mainly affects how much memory will be taken by the logged messages.
* A smaller value means less memory, but will increase the execution time due to the overhead of [[Logger::flush()]].
*/
public function setFlushInterval($value)
{
$this->getLogger()->flushInterval = $value;
}
/**
* Dispatches the logged messages to [[targets]].
* @param array $messages the logged messages
* @param bool $final whether this method is called at the end of the current application
*/
public function dispatch($messages, $final)
{
$targetErrors = [];
foreach ($this->targets as $target) {
if ($target->enabled) {
try {
$target->collect($messages, $final);
} catch (\Exception $e) {
$target->enabled = false;
$targetErrors[] = [
'Unable to send log via ' . get_class($target) . ': ' . ErrorHandler::convertExceptionToString($e),
Logger::LEVEL_WARNING,
__METHOD__,
microtime(true),
[],
];
}
}
}
if (!empty($targetErrors)) {
$this->dispatch($targetErrors, true);
}
}
}

379
framework/log/Logger.php

@ -7,75 +7,43 @@
namespace yii\log;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\LoggerTrait;
use Yii;
use yii\base\Component;
use yii\base\ErrorHandler;
/**
* 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 according to [[targets]].
*
* 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:
*
* - [[Yii::trace()]]
* - [[Yii::debug()]]
* - [[Yii::error()]]
* - [[Yii::warning()]]
* - [[Yii::info()]]
* - [[Yii::beginProfile()]]
* - [[Yii::endProfile()]]
*
* For more details and usage information on Logger, see the [guide article on logging](guide:runtime-logging).
* For more details and usage information on Logger, see the [guide article on logging](guide:runtime-logging)
* and [PSR-3 specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
*
* When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]]
* to send logged messages to different log targets, such as [[FileTarget|file]], [[EmailTarget|email]],
* or [[DbTarget|database]], with the help of the [[dispatcher]].
* or [[DbTarget|database]], according to the [[targets]].
*
* @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.
* @property array|Target[] $targets the log targets. See [[setTargets()]] for details.
* @property float $elapsedTime The total elapsed time in seconds for current request. This property is
* read-only.
* @property array $profiling The profiling results. Each element is an array consisting of these elements:
* `info`, `category`, `timestamp`, `trace`, `level`, `duration`, `memory`, `memoryDiff`. The `memory` and
* `memoryDiff` values are available since version 2.0.11. This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Logger extends Component
class Logger extends Component implements LoggerInterface
{
/**
* Error message level. An error message is one that indicates the abnormal termination of the
* application and may require developer's handling.
*/
const LEVEL_ERROR = 0x01;
/**
* Warning message level. A warning message is one that indicates some abnormal happens but
* the application is able to continue to run. Developers should pay attention to this message.
*/
const LEVEL_WARNING = 0x02;
/**
* Informational message level. An informational message is one that includes certain information
* for developers to review.
*/
const LEVEL_INFO = 0x04;
/**
* Tracing message level. An tracing message is one that reveals the code execution flow.
*/
const LEVEL_TRACE = 0x08;
/**
* Profiling message level. This indicates the message is for profiling purpose.
*/
const LEVEL_PROFILE = 0x40;
/**
* Profiling message level. This indicates the message is for profiling purpose. It marks the
* beginning of a profiling block.
*/
const LEVEL_PROFILE_BEGIN = 0x50;
/**
* Profiling message level. This indicates the message is for profiling purpose. It marks the
* end of a profiling block.
*/
const LEVEL_PROFILE_END = 0x60;
use LoggerTrait;
/**
* @var array logged messages. This property is managed by [[log()]] and [[flush()]].
@ -83,14 +51,18 @@ class Logger extends Component
*
* ```
* [
* [0] => message (mixed, can be a string or some complex data, such as an exception object)
* [1] => level (integer)
* [2] => category (string)
* [3] => timestamp (float, obtained by microtime(true))
* [4] => traces (array, debug backtrace, contains the application code call stacks)
* [5] => memory usage in bytes (int, obtained by memory_get_usage()), available since version 2.0.11.
* [0] => level (string)
* [1] => message (mixed, can be a string or some complex data, such as an exception object)
* [2] => context (array)
* ]
* ```
*
* Message context has a following keys:
*
* - category: string, message category.
* - time: float, message timestamp obtained by microtime(true).
* - trace: array, debug backtrace, contains the application code call stacks.
* - memory: int, memory usage in bytes, obtained by `memory_get_usage()`, available since version 2.0.11.
*/
public $messages = [];
/**
@ -107,11 +79,65 @@ class Logger extends Component
* call stacks are counted.
*/
public $traceLevel = 0;
/**
* @var array|Target[] the log targets. Each array element represents a single [[Target|log target]] instance
* or the configuration for creating the log target instance.
* @since 2.1
*/
private $_targets = [];
/**
* @var bool whether [[targets]] have been initialized, e.g. ensured to be objects.
* @since 2.1
*/
private $_isTargetsInitialized = false;
/**
* @return Target[] the log targets. Each array element represents a single [[Target|log target]] instance.
* @since 2.1
*/
public function getTargets()
{
if (!$this->_isTargetsInitialized) {
foreach ($this->_targets as $name => $target) {
if (!$target instanceof Target) {
$this->_targets[$name] = Yii::createObject($target);
}
}
$this->_isTargetsInitialized = true;
}
return $this->_targets;
}
/**
* @var Dispatcher the message dispatcher
* @param array|Target[] $targets the log targets. Each array element represents a single [[Target|log target]] instance
* or the configuration for creating the log target instance.
* @since 2.1
*/
public $dispatcher;
public function setTargets($targets)
{
$this->_targets = $targets;
$this->_isTargetsInitialized = false;
}
/**
* Adds extra target to [[targets]].
* @param Target|array $target the log target instance or its DI compatible configuration.
* @param string|null $name array key to be used to store target, if `null` is given target will be append
* to the end of the array by natural integer key.
*/
public function addTarget($target, $name = null)
{
if (!$target instanceof Target) {
$this->_isTargetsInitialized = false;
}
if ($name === null) {
$this->_targets[] = $target;
} else {
$this->_targets[$name] = $target;
}
}
/**
* Initializes the logger by registering [[flush()]] as a shutdown function.
@ -129,35 +155,63 @@ 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|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 int $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`.
* @param string $category the category of the message.
* {@inheritdoc}
*/
public function log($message, $level, $category = 'application')
public function log($level, $message, array $context = array())
{
$time = microtime(true);
$traces = [];
if ($this->traceLevel > 0) {
$count = 0;
$ts = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
array_pop($ts); // remove the last trace since it would be the entry script, not very useful
foreach ($ts as $trace) {
if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII2_PATH) !== 0) {
unset($trace['object'], $trace['args']);
$traces[] = $trace;
if (++$count >= $this->traceLevel) {
break;
if (!is_string($message)) {
if (is_scalar($message)) {
$message = (string)$message;
} elseif (is_object($message)) {
if ($message instanceof \Throwable) {
if (!isset($context['exception'])) {
$context['exception'] = $message;
}
$message = $message->__toString();
} elseif (method_exists($message, '__toString')) {
$message = $message->__toString();
} else {
throw new InvalidArgumentException('The log message MUST be a string or object implementing __toString()');
}
} else {
throw new InvalidArgumentException('The log message MUST be a string or object implementing __toString()');
}
}
if (!isset($context['time'])) {
$context['time'] = microtime(true);
}
if (!isset($context['trace'])) {
$traces = [];
if ($this->traceLevel > 0) {
$count = 0;
$ts = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
array_pop($ts); // remove the last trace since it would be the entry script, not very useful
foreach ($ts as $trace) {
if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII2_PATH) !== 0) {
unset($trace['object'], $trace['args']);
$traces[] = $trace;
if (++$count >= $this->traceLevel) {
break;
}
}
}
}
$context['trace'] = $traces;
}
if (!isset($context['memory'])) {
$context['memory'] = memory_get_usage();
}
if (!isset($context['category'])) {
$context['category'] = 'application';
}
$this->messages[] = [$message, $level, $category, $time, $traces, memory_get_usage()];
$message = $this->parseMessage($message, $context);
$this->messages[] = [$level, $message, $context];
if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) {
$this->flush();
}
@ -173,157 +227,82 @@ class Logger extends Component
// https://github.com/yiisoft/yii2/issues/5619
// new messages could be logged while the existing ones are being handled by targets
$this->messages = [];
if ($this->dispatcher instanceof Dispatcher) {
$this->dispatcher->dispatch($messages, $final);
}
}
/**
* Returns the total elapsed time since the start of the current request.
* This method calculates the difference between now and the timestamp
* defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning
* of [[\yii\BaseYii]] class file.
* @return float the total elapsed time in seconds for current request.
*/
public function getElapsedTime()
{
return microtime(true) - YII_BEGIN_TIME;
$this->dispatch($messages, $final);
}
/**
* Returns 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\Connection`.
* @param array $excludeCategories list of categories that you want to exclude
* @return array the profiling results. Each element is an array consisting of these elements:
* `info`, `category`, `timestamp`, `trace`, `level`, `duration`, `memory`, `memoryDiff`.
* The `memory` and `memoryDiff` values are available since version 2.0.11.
* Dispatches the logged messages to [[targets]].
* @param array $messages the logged messages
* @param bool $final whether this method is called at the end of the current application
* @since 2.1
*/
public function getProfiling($categories = [], $excludeCategories = [])
protected function dispatch($messages, $final)
{
$timings = $this->calculateTimings($this->messages);
if (empty($categories) && empty($excludeCategories)) {
return $timings;
}
foreach ($timings as $i => $timing) {
$matched = empty($categories);
foreach ($categories as $category) {
$prefix = rtrim($category, '*');
if (($timing['category'] === $category || $prefix !== $category) && strpos($timing['category'], $prefix) === 0) {
$matched = true;
break;
}
}
if ($matched) {
foreach ($excludeCategories as $category) {
$prefix = rtrim($category, '*');
foreach ($timings as $i => $timing) {
if (($timing['category'] === $category || $prefix !== $category) && strpos($timing['category'], $prefix) === 0) {
$matched = false;
break;
}
}
$targetErrors = [];
foreach ($this->targets as $target) {
if ($target->enabled) {
try {
$target->collect($messages, $final);
} catch (\Exception $e) {
$target->enabled = false;
$targetErrors[] = [
'Unable to send log via ' . get_class($target) . ': ' . ErrorHandler::convertExceptionToString($e),
LogLevel::WARNING,
__METHOD__,
microtime(true),
[],
];
}
}
if (!$matched) {
unset($timings[$i]);
}
}
return array_values($timings);
if (!empty($targetErrors)) {
$this->dispatch($targetErrors, true);
}
}
/**
* Returns the statistical results of DB queries.
* The results returned include the number of SQL statements executed and
* the total time spent.
* @return array the first element indicates the number of SQL statements executed,
* and the second element the total time spent in SQL execution.
* Parses log message resolving placeholders in the form: '{foo}', where foo
* will be replaced by the context data in key "foo".
* @param string $message log message.
* @param array $context message context.
* @return string parsed message.
* @since 2.1
*/
public function getDbProfiling()
protected function parseMessage($message, array $context)
{
$timings = $this->getProfiling([
'yii\db\Command::query',
'yii\db\Command::execute',
]);
$count = count($timings);
$time = 0;
foreach ($timings as $timing) {
$time += $timing['duration'];
}
return [$count, $time];
return preg_replace_callback('/\\{([\\w\\.]+)\\}/is', function ($matches) use ($context) {
$placeholderName = $matches[1];
if (isset($context[$placeholderName])) {
return (string)$context[$placeholderName];
}
return $matches[0];
}, $message);
}
/**
* Calculates the elapsed time for the given log messages.
* @param array $messages the log messages obtained from profiling
* @return array timings. Each element is an array consisting of these elements:
* `info`, `category`, `timestamp`, `trace`, `level`, `duration`, `memory`, `memoryDiff`.
* The `memory` and `memoryDiff` values are available since version 2.0.11.
* Returns the total elapsed time since the start of the current request.
* This method calculates the difference between now and the timestamp
* defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning
* of [[\yii\BaseYii]] class file.
* @return float the total elapsed time in seconds for current request.
*/
public function calculateTimings($messages)
public function getElapsedTime()
{
$timings = [];
$stack = [];
foreach ($messages as $i => $log) {
[$token, $level, $category, $timestamp, $traces] = $log;
$memory = $log[5] ?? 0;
$log[6] = $i;
$hash = md5(json_encode($token));
if ($level == self::LEVEL_PROFILE_BEGIN) {
$stack[$hash] = $log;
} elseif ($level == self::LEVEL_PROFILE_END) {
if (isset($stack[$hash])) {
$timings[$stack[$hash][6]] = [
'info' => $stack[$hash][0],
'category' => $stack[$hash][2],
'timestamp' => $stack[$hash][3],
'trace' => $stack[$hash][4],
'level' => count($stack) - 1,
'duration' => $timestamp - $stack[$hash][3],
'memory' => $memory,
'memoryDiff' => $memory - ($stack[$hash][5] ?? 0),
];
unset($stack[$hash]);
}
}
}
ksort($timings);
return array_values($timings);
return microtime(true) - YII_BEGIN_TIME;
}
/**
* Returns the text display of the specified level.
* @param int $level the message level, e.g. [[LEVEL_ERROR]], [[LEVEL_WARNING]].
* @param mixed $level the message level, e.g. [[LogLevel::ERROR]], [[LogLevel::WARNING]].
* @return string the text display of the level
*/
public static function getLevelName($level)
{
static $levels = [
self::LEVEL_ERROR => 'error',
self::LEVEL_WARNING => 'warning',
self::LEVEL_INFO => 'info',
self::LEVEL_TRACE => 'trace',
self::LEVEL_PROFILE_BEGIN => 'profile begin',
self::LEVEL_PROFILE_END => 'profile end',
self::LEVEL_PROFILE => 'profile',
];
return isset($levels[$level]) ? $levels[$level] : 'unknown';
if (is_string($level)) {
return $level;
}
return 'unknown';
}
}

91
framework/log/LoggerTarget.php

@ -0,0 +1,91 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\log;
use Psr\Log\LoggerInterface;
use yii\base\InvalidConfigException;
use yii\di\Instance;
/**
* PsrTarget is a log target which simply passes messages to another PSR-3 compatible logger,
* which is specified via [[$logger]].
*
* Application configuration example:
*
* ```php
* return [
* 'logger' => [
* 'targets' => [
* [
* 'class' => yii\log\LoggerTarget::class,
* 'logger' => function () {
* $logger = new \Monolog\Logger('my_logger');
* $logger->pushHandler(new \Monolog\Handler\SlackHandler('slack_token', 'logs', null, true, null, \Monolog\Logger::DEBUG));
* return $logger;
* },
* ],
* ],
* // ...
* ],
* // ...
* ];
* ```
*
* > Warning: make sure logger specified via [[$logger]] is not the same as [[Yii::getLogger()]], otherwise
* your program may fall into infinite loop.
*
* @property LoggerInterface $logger logger to be used by this target. Refer to [[setLogger()]] for details.
*
* @author Paul Klimov <klimov-paul@gmail.com>
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.1
*/
class LoggerTarget extends Target
{
/**
* @var LoggerInterface logger instance to be used for messages processing.
*/
private $_logger;
/**
* Sets the PSR-3 logger used to save messages of this target.
* @param LoggerInterface|\Closure|array $logger logger instance or its DI compatible configuration.
* @throws InvalidConfigException
*/
public function setLogger($logger)
{
if ($logger instanceof \Closure) {
$logger = call_user_func($logger);
}
$this->_logger = Instance::ensure($logger, LoggerInterface::class);
}
/**
* @return LoggerInterface logger instance.
* @throws InvalidConfigException if logger is not set.
*/
public function getLogger()
{
if ($this->_logger === null) {
throw new InvalidConfigException('"' . get_class($this) . '::$logger" must be set to be "' . LoggerInterface::class . '" instance');
}
return $this->_logger;
}
/**
* {@inheritdoc}
*/
public function export()
{
foreach ($this->messages as $message) {
[$level, $text, $context] = $message;
$this->getLogger()->log($level, $text, $context);
}
}
}

44
framework/log/SyslogTarget.php

@ -7,8 +7,8 @@
namespace yii\log;
use Psr\Log\LogLevel;
use Yii;
use yii\helpers\VarDumper;
/**
* SyslogTarget writes log to syslog.
@ -27,6 +27,20 @@ class SyslogTarget extends Target
*/
public $facility = LOG_USER;
/**
* @var array syslog levels
*/
private $_syslogLevels = [
LogLevel::EMERGENCY => LOG_EMERG,
LogLevel::ALERT => LOG_ALERT,
LogLevel::CRITICAL => LOG_CRIT,
LogLevel::ERROR => LOG_ERR,
LogLevel::WARNING => LOG_WARNING,
LogLevel::NOTICE => LOG_NOTICE,
LogLevel::INFO => LOG_INFO,
LogLevel::DEBUG => LOG_DEBUG,
];
/**
* @var int openlog options. This is a bitfield passed as the `$option` parameter to [openlog()](http://php.net/openlog).
* Defaults to `null` which means to use the default options `LOG_ODELAY | LOG_PID`.
* @see http://php.net/openlog for available options.
@ -34,19 +48,6 @@ class SyslogTarget extends Target
*/
public $options;
/**
* @var array syslog levels
*/
private $_syslogLevels = [
Logger::LEVEL_TRACE => LOG_DEBUG,
Logger::LEVEL_PROFILE_BEGIN => LOG_DEBUG,
Logger::LEVEL_PROFILE_END => LOG_DEBUG,
Logger::LEVEL_PROFILE => LOG_DEBUG,
Logger::LEVEL_INFO => LOG_INFO,
Logger::LEVEL_WARNING => LOG_WARNING,
Logger::LEVEL_ERROR => LOG_ERR,
];
/**
* @inheritdoc
@ -66,7 +67,7 @@ class SyslogTarget extends Target
{
openlog($this->identity, $this->options, $this->facility);
foreach ($this->messages as $message) {
syslog($this->_syslogLevels[$message[1]], $this->formatMessage($message));
syslog($this->_syslogLevels[$message[0]], $this->formatMessage($message));
}
closelog();
}
@ -76,18 +77,9 @@ class SyslogTarget extends Target
*/
public function formatMessage($message)
{
[$text, $level, $category, $timestamp] = $message;
[$level, $text, $context] = $message;
$level = Logger::getLevelName($level);
if (!is_string($text)) {
// exceptions may not be serializable if in the call stack somewhere is a Closure
if ($text instanceof \Throwable) {
$text = (string) $text;
} else {
$text = VarDumper::export($text);
}
}
$prefix = $this->getMessagePrefix($message);
return "{$prefix}[$level][$category] $text";
return $prefix. '[' . $level . '][' . ($context['category'] ?? '') . '] ' .$text;
}
}

113
framework/log/Target.php

@ -7,9 +7,9 @@
namespace yii\log;
use Psr\Log\LogLevel;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\VarDumper;
use yii\web\Request;
@ -25,10 +25,6 @@ use yii\web\Request;
* satisfying both filter conditions will be handled. Additionally, you
* may specify [[except]] to exclude messages of certain categories.
*
* @property int $levels The message levels that this target is interested in. This is a bitmap of level
* values. Defaults to 0, meaning all available levels. Note that the type of this property differs in getter
* and setter. See [[getLevels()]] and [[setLevels()]] for details.
*
* For more details and usage information on Target, see the [guide article on logging & targets](guide:runtime-logging).
*
* @author Qiang Xue <qiang.xue@gmail.com>
@ -57,6 +53,22 @@ abstract class Target extends Component
*/
public $except = [];
/**
* @var array the message levels that this target is interested in.
*
* The parameter should be an array of interested level names. See [[LogLevel]] constants for valid level names.
*
* For example:
*
* ```php
* ['error', 'warning'],
* // or
* [LogLevel::ERROR, LogLevel::WARNING]
* ```
*
* Defaults is empty array, meaning all available levels.
*/
public $levels = [];
/**
* @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.
*
@ -93,8 +105,6 @@ abstract class Target extends Component
*/
public $messages = [];
private $_levels = 0;
/**
* Exports log [[messages]] to a specific destination.
@ -112,11 +122,11 @@ abstract class Target extends Component
*/
public function collect($messages, $final)
{
$this->messages = array_merge($this->messages, static::filterMessages($messages, $this->getLevels(), $this->categories, $this->except));
$this->messages = array_merge($this->messages, static::filterMessages($messages, $this->levels, $this->categories, $this->except));
$count = count($this->messages);
if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
if (($context = $this->getContextMessage()) !== '') {
$this->messages[] = [$context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME];
$this->messages[] = [LogLevel::INFO, $context, ['category' => 'application', 'time' => YII_BEGIN_TIME]];
}
// set exportInterval to 0 to avoid triggering export again while exporting
$oldExportInterval = $this->exportInterval;
@ -144,84 +154,25 @@ abstract class Target extends Component
}
/**
* @return int the message levels that this target is interested in. This is a bitmap of
* level values. Defaults to 0, meaning all available levels.
*/
public function getLevels()
{
return $this->_levels;
}
/**
* Sets the message levels that this target is interested in.
*
* The parameter can be either an array of interested level names or an integer representing
* the bitmap of the interested level values. Valid level names include: 'error',
* 'warning', 'info', 'trace' and 'profile'; valid level values include:
* [[Logger::LEVEL_ERROR]], [[Logger::LEVEL_WARNING]], [[Logger::LEVEL_INFO]],
* [[Logger::LEVEL_TRACE]] and [[Logger::LEVEL_PROFILE]].
*
* For example,
*
* ```php
* ['error', 'warning']
* // which is equivalent to:
* Logger::LEVEL_ERROR | Logger::LEVEL_WARNING
* ```
*
* @param array|int $levels message levels that this target is interested in.
* @throws InvalidConfigException if $levels value is not correct.
*/
public function setLevels($levels)
{
static $levelMap = [
'error' => Logger::LEVEL_ERROR,
'warning' => Logger::LEVEL_WARNING,
'info' => Logger::LEVEL_INFO,
'trace' => Logger::LEVEL_TRACE,
'profile' => Logger::LEVEL_PROFILE,
];
if (is_array($levels)) {
$this->_levels = 0;
foreach ($levels as $level) {
if (isset($levelMap[$level])) {
$this->_levels |= $levelMap[$level];
} else {
throw new InvalidConfigException("Unrecognized level: $level");
}
}
} else {
$bitmapValues = array_reduce($levelMap, function ($carry, $item) {
return $carry | $item;
});
if (!($bitmapValues & $levels) && $levels !== 0) {
throw new InvalidConfigException("Incorrect $levels value");
}
$this->_levels = $levels;
}
}
/**
* Filters the given messages according to their categories and levels.
* @param array $messages messages to be filtered.
* The message structure follows that in [[Logger::messages]].
* @param int $levels the message levels to filter by. This is a bitmap of
* level values. Value 0 means allowing all levels.
* @param array $levels the message levels to filter by. Empty value means allowing all levels.
* @param array $categories the message categories to filter by. If empty, it means all categories are allowed.
* @param array $except the message categories to exclude. If empty, it means all categories are allowed.
* @return array the filtered messages.
*/
public static function filterMessages($messages, $levels = 0, $categories = [], $except = [])
public static function filterMessages($messages, $levels = [], $categories = [], $except = [])
{
foreach ($messages as $i => $message) {
if ($levels && !($levels & $message[1])) {
if (!empty($levels) && !in_array($message[0], $levels, true)) {
unset($messages[$i]);
continue;
}
$matched = empty($categories);
foreach ($categories as $category) {
if ($message[2] === $category || !empty($category) && substr_compare($category, '*', -1, 1) === 0 && strpos($message[2], rtrim($category, '*')) === 0) {
if ($message[2]['category'] === $category || !empty($category) && substr_compare($category, '*', -1, 1) === 0 && strpos($message[2]['category'], rtrim($category, '*')) === 0) {
$matched = true;
break;
}
@ -230,7 +181,7 @@ abstract class Target extends Component
if ($matched) {
foreach ($except as $category) {
$prefix = rtrim($category, '*');
if (($message[2] === $category || $prefix !== $category) && strpos($message[2], $prefix) === 0) {
if (($message[2]['category'] === $category || $prefix !== $category) && strpos($message[2]['category'], $prefix) === 0) {
$matched = false;
break;
}
@ -252,19 +203,13 @@ abstract class Target extends Component
*/
public function formatMessage($message)
{
[$text, $level, $category, $timestamp] = $message;
[$level, $text, $context] = $message;
$category = $context['category'];
$timestamp = $context['time'];
$level = Logger::getLevelName($level);
if (!is_string($text)) {
// exceptions may not be serializable if in the call stack somewhere is a Closure
if ($text instanceof \Throwable) {
$text = (string) $text;
} else {
$text = VarDumper::export($text);
}
}
$traces = [];
if (isset($message[4])) {
foreach ($message[4] as $trace) {
if (isset($context['trace'])) {
foreach ($context['trace'] as $trace) {
$traces[] = "in {$trace['file']}:{$trace['line']}";
}
}

7
framework/log/migrations/m141106_185632_log_init.php

@ -33,10 +33,13 @@ class m141106_185632_log_init extends Migration
protected function getDbTargets()
{
if ($this->dbTargets === []) {
$log = Yii::$app->getLog();
$logger = Yii::getLogger();
if (!$logger instanceof \yii\log\Logger) {
throw new InvalidConfigException('You should configure "logger" to be instance of "\yii\log\Logger" before executing this migration.');
}
$usedTargets = [];
foreach ($log->targets as $target) {
foreach ($logger->targets as $target) {
if ($target instanceof DbTarget) {
$currentTarget = [
$target->db,

89
framework/profile/LogTarget.php

@ -0,0 +1,89 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\profile;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Yii;
use yii\di\Instance;
/**
* LogTarget saves profiling messages as a log messages.
*
* Application configuration example:
*
* ```php
* return [
* 'profiler' => [
* 'targets' => [
* [
* 'class' => yii\profile\LogTarget::class,
* ],
* ],
* // ...
* ],
* // ...
* ];
* ```
*
* @property LoggerInterface $logger logger to be used for message export.
*
* @author Paul Klimov <klimov-paul@gmail.com>
* @since 2.1
*/
class LogTarget extends Target
{
/**
* @var string log level to be used for messages export.
*/
public $logLevel = LogLevel::DEBUG;
/**
* @var LoggerInterface logger to be used for message export.
*/
private $_logger;
/**
* @return LoggerInterface logger to be used for message saving.
*/
public function getLogger()
{
if ($this->_logger === null) {
$this->_logger = Yii::getLogger();
}
return $this->_logger;
}
/**
* @param LoggerInterface|\Closure|array $logger logger instance or its DI compatible configuration.
*/
public function setLogger($logger)
{
if ($logger === null) {
$this->_logger = null;
return;
}
if ($logger instanceof \Closure) {
$logger = call_user_func($logger);
}
$this->_logger = Instance::ensure($logger, LoggerInterface::class);
}
/**
* {@inheritdoc}
*/
public function export(array $messages)
{
$logger = $this->getLogger();
foreach ($messages as $message) {
$message['time'] = $message['beginTime'];
$logger->log($this->logLevel, $message['token'], $message);
}
}
}

216
framework/profile/Profiler.php

@ -0,0 +1,216 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\profile;
use Yii;
use yii\base\Component;
use yii\base\InvalidArgumentException;
/**
* Profiler provides profiling support. It stores profiling messages in the memory and sends them to different targets
* according to [[targets]].
*
* A Profiler instance can be accessed via `Yii::getProfiler()`.
*
* For convenience, a set of shortcut methods are provided for profiling via the [[Yii]] class:
*
* - [[Yii::beginProfile()]]
* - [[Yii::endProfile()]]
*
* For more details and usage information on Profiler, see the [guide article on profiling](guide:runtime-profiling)
*
* @author Paul Klimov <klimov-paul@gmail.com>
* @since 2.1
*/
class Profiler extends Component implements ProfilerInterface
{
/**
* @var bool whether to profiler is enabled. Defaults to true.
* You may use this field to disable writing of the profiling messages and thus save the memory usage.
*/
public $enabled = true;
/**
* @var array[] complete profiling messages.
* Each message has a following keys:
*
* - token: string, profiling token.
* - category: string, message category.
* - beginTime: float, profiling begin timestamp obtained by microtime(true).
* - endTime: float, profiling end timestamp obtained by microtime(true).
* - duration: float, profiling block duration in milliseconds.
* - beginMemory: int, memory usage at the beginning of profile block in bytes, obtained by `memory_get_usage()`.
* - endMemory: int, memory usage at the end of profile block in bytes, obtained by `memory_get_usage()`.
* - memoryDiff: int, a diff between 'endMemory' and 'beginMemory'.
*/
public $messages = [];
/**
* @var array pending profiling messages, e.g. the ones which have begun but not ended yet.
*/
private $_pendingMessages = [];
/**
* @var array|Target[] the profiling targets. Each array element represents a single [[Target|profiling target]] instance
* or the configuration for creating the profiling target instance.
* @since 2.1
*/
private $_targets = [];
/**
* @var bool whether [[targets]] have been initialized, e.g. ensured to be objects.
* @since 2.1
*/
private $_isTargetsInitialized = false;
/**
* Initializes the profiler by registering [[flush()]] as a shutdown function.
*/
public function init()
{
parent::init();
register_shutdown_function([$this, 'flush']);
}
/**
* @return Target[] the profiling targets. Each array element represents a single [[Target|profiling target]] instance.
*/
public function getTargets()
{
if (!$this->_isTargetsInitialized) {
foreach ($this->_targets as $name => $target) {
if (!$target instanceof Target) {
$this->_targets[$name] = Yii::createObject($target);
}
}
$this->_isTargetsInitialized = true;
}
return $this->_targets;
}
/**
* @param array|Target[] $targets the profiling targets. Each array element represents a single [[Target|profiling target]] instance
* or the configuration for creating the profiling target instance.
*/
public function setTargets($targets)
{
$this->_targets = $targets;
$this->_isTargetsInitialized = false;
}
/**
* Adds extra target to [[targets]].
* @param Target|array $target the log target instance or its DI compatible configuration.
* @param string|null $name array key to be used to store target, if `null` is given target will be append
* to the end of the array by natural integer key.
*/
public function addTarget($target, $name = null)
{
if (!$target instanceof Target) {
$this->_isTargetsInitialized = false;
}
if ($name === null) {
$this->_targets[] = $target;
} else {
$this->_targets[$name] = $target;
}
}
/**
* {@inheritdoc}
*/
public function begin($token, array $context = [])
{
if (!$this->enabled) {
return;
}
$category = isset($context['category']) ?: 'application';
$message = array_merge($context, [
'token' => $token,
'category' => $category,
'beginTime' => microtime(true),
'beginMemory' => memory_get_usage(),
]);
$this->_pendingMessages[$category][$token][] = $message;
}
/**
* {@inheritdoc}
*/
public function end($token, array $context = [])
{
if (!$this->enabled) {
return;
}
$category = isset($context['category']) ?: 'application';
if (empty($this->_pendingMessages[$category][$token])) {
throw new InvalidArgumentException('Unexpected ' . get_called_class() . '::end() call for category "' . $category . '" token "' . $token . '". A matching begin() is not found.');
}
$message = array_pop($this->_pendingMessages[$category][$token]);
if (empty($this->_pendingMessages[$category][$token])) {
unset($this->_pendingMessages[$category][$token]);
if (empty($this->_pendingMessages[$category])) {
unset($this->_pendingMessages[$category]);
}
}
$message = array_merge(
$message,
$context,
[
'endTime' => microtime(true),
'endMemory' => memory_get_usage(),
]
);
$message['duration'] = $message['endTime'] - $message['beginTime'];
$message['memoryDiff'] = $message['endMemory'] - $message['beginMemory'];
$this->messages[] = $message;
}
/**
* {@inheritdoc}
*/
public function flush()
{
foreach ($this->_pendingMessages as $category => $categoryMessages) {
foreach ($categoryMessages as $token => $messages) {
if (!empty($messages)) {
Yii::warning('Unclosed profiling entry detected: category "' . $category . '" token "' . $token . '"', __METHOD__);
}
}
}
$this->_pendingMessages = [];
if (empty($this->messages)) {
return;
}
$messages = $this->messages;
// new messages could appear while the existing ones are being handled by targets
$this->messages = [];
$this->dispatch($messages);
}
/**
* Dispatches the profiling messages to [[targets]].
* @param array $messages the profiling messages.
*/
protected function dispatch($messages)
{
foreach ($this->getTargets() as $target) {
$target->collect($messages);
}
}
}

54
framework/profile/ProfilerInterface.php

@ -0,0 +1,54 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\profile;
/**
* ProfilerInterface describes a profiler instance.
*
* A Profiler instance can be accessed via `Yii::getProfiler()`.
*
* For more details and usage information on Profiler, see the [guide article on profiling](guide:runtime-profiling)
*
* @author Paul Klimov <klimov-paul@gmail.com>
* @since 2.1
*/
interface ProfilerInterface
{
/**
* Marks the beginning of a code block for profiling.
* This has to be matched with a call to [[end()]] with the same category name.
* The begin- and end- calls must also be properly nested. For example,
*
* ```php
* \Yii::getProfiler()->begin('block1');
* // some code to be profiled
* \Yii::getProfiler()->begin('block2');
* // some other code to be profiled
* \Yii::getProfiler()->end('block2');
* \Yii::getProfiler()->end('block1');
* ```
* @param string $token token for the code block
* @param array $context the context data of this profile block
* @see endProfile()
*/
public function begin($token, array $context = []);
/**
* Marks the end of a code block for profiling.
* This has to be matched with a previous call to [[begin()]] with the same category name.
* @param string $token token for the code block
* @param array $context the context data of this profile block
* @see begin()
*/
public function end($token, array $context = []);
/**
* Flushes profiling messages from memory to actual storage.
*/
public function flush();
}

106
framework/profile/Target.php

@ -0,0 +1,106 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\profile;
use yii\base\Component;
/**
* Target is the base class for all profiling target classes.
*
* A profile target object will filter the messages stored by [[Profiler]] according
* to its [[categories]] and [[except]] properties.
*
* For more details and usage information on Target, see the [guide article on profiling & targets](guide:runtime-profiling).
*
* @author Paul Klimov <klimov-paul@gmail.com>
* @since 2.1
*/
abstract class Target extends Component
{
/**
* @var bool whether to enable this log target. Defaults to true.
*/
public $enabled = true;
/**
* @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\Connection`.
*/
public $categories = [];
/**
* @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\Connection`.
* @see categories
*/
public $except = [];
/**
* 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 profiling messages to be processed. See [[Logger::messages]] for the structure
* of each message.
*/
public function collect(array $messages)
{
if (!$this->enabled) {
return;
}
$messages = $this->filterMessages($messages);
if (count($messages) > 0) {
$this->export($messages);
}
}
/**
* Exports profiling messages to a specific destination.
* Child classes must implement this method.
* @param array $messages profiling messages to be exported.
*/
abstract public function export(array $messages);
/**
* Filters the given messages according to their categories.
* @param array $messages messages to be filtered.
* The message structure follows that in [[Logger::messages]].
* @return array the filtered messages.
*/
protected function filterMessages($messages)
{
foreach ($messages as $i => $message) {
$matched = empty($this->categories);
foreach ($this->categories as $category) {
if ($message['category'] === $category || !empty($category) && substr_compare($category, '*', -1, 1) === 0 && strpos($message['category'], rtrim($category, '*')) === 0) {
$matched = true;
break;
}
}
if ($matched) {
foreach ($this->except as $category) {
$prefix = rtrim($category, '*');
if (($message['category'] === $category || $prefix !== $category) && strpos($message['category'], $prefix) === 0) {
$matched = false;
break;
}
}
}
if (!$matched) {
unset($messages[$i]);
}
}
return $messages;
}
}

4
framework/rbac/DbManager.php

@ -155,7 +155,7 @@ class DbManager extends BaseManager
$item = $this->items[$itemName];
Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
Yii::debug($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
if (!$this->executeRule($user, $item, $params)) {
return false;
@ -194,7 +194,7 @@ class DbManager extends BaseManager
return false;
}
Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
Yii::debug($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
if (!$this->executeRule($user, $item, $params)) {
return false;

2
framework/rbac/PhpManager.php

@ -133,7 +133,7 @@ class PhpManager extends BaseManager
/* @var $item Item */
$item = $this->items[$itemName];
Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission : $itemName", __METHOD__);
Yii::debug($item instanceof Role ? "Checking role: $itemName" : "Checking permission : $itemName", __METHOD__);
if (!$this->executeRule($user, $item, $params)) {
return false;

2
framework/rest/UrlRule.php

@ -225,7 +225,7 @@ class UrlRule extends CompositeUrlRule
/* @var $rule WebUrlRule */
$result = $rule->parseRequest($manager, $request);
if (YII_DEBUG) {
Yii::trace([
Yii::debug([
'rule' => method_exists($rule, '__toString') ? $rule->__toString() : get_class($rule),
'match' => $result !== false,
'parent' => self::class

2
framework/web/Application.php

@ -97,7 +97,7 @@ class Application extends \yii\base\Application
unset($params[0]);
}
try {
Yii::trace("Route requested: '$route'", __METHOD__);
Yii::debug("Route requested: '$route'", __METHOD__);
$this->requestedRoute = $route;
$result = $this->runAction($route, $params);
if ($result instanceof Response) {

2
framework/web/AssetConverter.php

@ -108,7 +108,7 @@ class AssetConverter extends Component implements AssetConverterInterface
$status = proc_close($proc);
if ($status === 0) {
Yii::trace("Converted $asset into $result:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
Yii::debug("Converted $asset into $result:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
} elseif (YII_DEBUG) {
throw new Exception("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
} else {

2
framework/web/CompositeUrlRule.php

@ -57,7 +57,7 @@ abstract class CompositeUrlRule extends BaseObject implements UrlRuleInterface
/* @var $rule UrlRule */
$result = $rule->parseRequest($manager, $request);
if (YII_DEBUG) {
Yii::trace([
Yii::debug([
'rule' => method_exists($rule, '__toString') ? $rule->__toString() : get_class($rule),
'match' => $result !== false,
'parent' => self::class

6
framework/web/UrlManager.php

@ -257,7 +257,7 @@ class UrlManager extends Component
foreach ($this->rules as $rule) {
$result = $rule->parseRequest($this, $request);
if (YII_DEBUG) {
Yii::trace([
Yii::debug([
'rule' => method_exists($rule, '__toString') ? $rule->__toString() : get_class($rule),
'match' => $result !== false,
'parent' => null,
@ -272,7 +272,7 @@ class UrlManager extends Component
return false;
}
Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__);
Yii::debug('No matching URL rules. Using default URL parsing logic.', __METHOD__);
$suffix = (string) $this->suffix;
$pathInfo = $request->getPathInfo();
@ -302,7 +302,7 @@ class UrlManager extends Component
return [$pathInfo, []];
}
Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
Yii::debug('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
$route = $request->getQueryParam($this->routeParam, '');
if (is_array($route)) {
$route = '';

2
framework/web/UrlRule.php

@ -434,7 +434,7 @@ class UrlRule extends BaseObject implements UrlRuleInterface
$route = $this->route;
}
Yii::trace("Request parsed with URL rule: {$this->name}", __METHOD__);
Yii::debug("Request parsed with URL rule: {$this->name}", __METHOD__);
if ($normalized) {
// pathInfo was changed by normalizer - we need also normalize route

134
tests/framework/BaseYiiTest.php

@ -7,10 +7,12 @@
namespace yiiunit\framework;
use Psr\Log\LogLevel;
use Yii;
use yii\BaseYii;
use yii\di\Container;
use yii\log\Logger;
use yii\profile\Profiler;
use yiiunit\data\base\Singer;
use yiiunit\TestCase;
@ -32,6 +34,8 @@ class BaseYiiTest extends TestCase
{
parent::tearDown();
Yii::$aliases = $this->aliases;
Yii::setLogger(null);
Yii::setProfiler(null);
}
public function testAlias()
@ -107,15 +111,64 @@ class BaseYiiTest extends TestCase
BaseYii::setLogger(null);
$defaultLogger = BaseYii::getLogger();
$this->assertInstanceOf(Logger::class, $defaultLogger);
BaseYii::setLogger(['flushInterval' => 789]);
$logger = BaseYii::getLogger();
$this->assertSame($defaultLogger, $logger);
$this->assertEquals(789, $logger->flushInterval);
BaseYii::setLogger(function() {
return new Logger();
});
$this->assertNotSame($defaultLogger, BaseYii::getLogger());
BaseYii::setLogger(null);
$defaultLogger = BaseYii::getLogger();
BaseYii::setLogger([
'class' => Logger::class,
'flushInterval' => 987,
]);
$logger = BaseYii::getLogger();
$this->assertNotSame($defaultLogger, $logger);
$this->assertEquals(987, $logger->flushInterval);
}
/**
* @covers \yii\BaseYii::setProfiler()
* @covers \yii\BaseYii::getProfiler()
*/
public function testSetupProfiler()
{
$profiler = new Profiler();
BaseYii::setProfiler($profiler);
$this->assertSame($profiler, BaseYii::getProfiler());
BaseYii::setProfiler(null);
$defaultProfiler = BaseYii::getProfiler();
$this->assertInstanceOf(Profiler::class, $defaultProfiler);
BaseYii::setProfiler(function() {
return new Profiler();
});
$this->assertNotSame($defaultProfiler, BaseYii::getProfiler());
BaseYii::setProfiler(null);
$defaultProfiler = BaseYii::getProfiler();
BaseYii::setProfiler([
'class' => Profiler::class,
]);
$profiler = BaseYii::getProfiler();
$this->assertNotSame($defaultProfiler, $profiler);
}
/**
* @depends testSetupLogger
*
* @covers \yii\BaseYii::info()
* @covers \yii\BaseYii::warning()
* @covers \yii\BaseYii::trace()
* @covers \yii\BaseYii::debug()
* @covers \yii\BaseYii::error()
* @covers \yii\BaseYii::beginProfile()
* @covers \yii\BaseYii::endProfile()
*/
public function testLog()
{
@ -124,36 +177,79 @@ class BaseYiiTest extends TestCase
->getMock();
BaseYii::setLogger($logger);
$logger->expects($this->exactly(6))
$logger->expects($this->exactly(4))
->method('log')
->withConsecutive(
[$this->equalTo('info message'), $this->equalTo(Logger::LEVEL_INFO), $this->equalTo('info category')],
[
$this->equalTo(LogLevel::INFO),
$this->equalTo('info message'),
$this->equalTo(['category' => 'info category'])
],
[
$this->equalTo(LogLevel::WARNING),
$this->equalTo('warning message'),
$this->equalTo(Logger::LEVEL_WARNING),
$this->equalTo('warning category'),
$this->equalTo(['category' => 'warning category']),
],
[$this->equalTo('trace message'), $this->equalTo(Logger::LEVEL_TRACE), $this->equalTo('trace category')],
[$this->equalTo('error message'), $this->equalTo(Logger::LEVEL_ERROR), $this->equalTo('error category')],
[
$this->equalTo('beginProfile message'),
$this->equalTo(Logger::LEVEL_PROFILE_BEGIN),
$this->equalTo('beginProfile category'),
$this->equalTo(LogLevel::DEBUG),
$this->equalTo('trace message'),
$this->equalTo(['category' => 'trace category'])
],
[
$this->equalTo('endProfile message'),
$this->equalTo(Logger::LEVEL_PROFILE_END),
$this->equalTo('endProfile category'),
$this->equalTo(LogLevel::ERROR),
$this->equalTo('error message'),
$this->equalTo(['category' => 'error category'])
]
);
BaseYii::info('info message', 'info category');
BaseYii::warning('warning message', 'warning category');
BaseYii::trace('trace message', 'trace category');
BaseYii::debug('trace message', 'trace category');
BaseYii::error('error message', 'error category');
BaseYii::beginProfile('beginProfile message', 'beginProfile category');
BaseYii::endProfile('endProfile message', 'endProfile category');
}
BaseYii::setLogger(null);
/**
* @depends testSetupProfiler
*
* @covers \yii\BaseYii::beginProfile()
* @covers \yii\BaseYii::endProfile()
*/
public function testProfile()
{
$profiler = $this->getMockBuilder('yii\profile\Profiler')
->setMethods(['begin', 'end'])
->getMock();
BaseYii::setProfiler($profiler);
$profiler->expects($this->exactly(2))
->method('begin')
->withConsecutive(
[
$this->equalTo('Profile message 1'),
$this->equalTo(['category' => 'Profile category 1'])
],
[
$this->equalTo('Profile message 2'),
$this->equalTo(['category' => 'Profile category 2']),
]
);
$profiler->expects($this->exactly(2))
->method('end')
->withConsecutive(
[
$this->equalTo('Profile message 1'),
$this->equalTo(['category' => 'Profile category 1'])
],
[
$this->equalTo('Profile message 2'),
$this->equalTo(['category' => 'Profile category 2']),
]
);
BaseYii::beginProfile('Profile message 1', 'Profile category 1');
BaseYii::endProfile('Profile message 1', 'Profile category 1');
BaseYii::beginProfile('Profile message 2', 'Profile category 2');
BaseYii::endProfile('Profile message 2', 'Profile category 2');
}
}

26
tests/framework/base/ApplicationTest.php

@ -7,11 +7,12 @@
namespace yiiunit\framework\base;
use Psr\Log\NullLogger;
use Yii;
use yii\base\BootstrapInterface;
use yii\base\Component;
use yii\base\Module;
use yii\log\Dispatcher;
use yii\log\Logger;
use yiiunit\TestCase;
/**
@ -24,13 +25,18 @@ class ApplicationTest extends TestCase
$this->mockApplication([
'container' => [
'definitions' => [
Dispatcher::class => DispatcherMock::class
Logger::class => NullLogger::class
],
],
'components' => [
'log' => [
'class' => Logger::class
],
],
'bootstrap' => ['log'],
]);
$this->assertInstanceOf(DispatcherMock::class, Yii::$app->log);
$this->assertInstanceOf(NullLogger::class, Yii::$app->log);
}
public function testBootstrap()
@ -61,18 +67,14 @@ class ApplicationTest extends TestCase
],
]);
$this->assertSame('Bootstrap with yii\base\Component', Yii::getLogger()->messages[0][0]);
$this->assertSame('Bootstrap with yiiunit\framework\base\BootstrapComponentMock::bootstrap()', Yii::getLogger()->messages[1][0]);
$this->assertSame('Loading module: moduleX', Yii::getLogger()->messages[2][0]);
$this->assertSame('Bootstrap with yii\base\Module', Yii::getLogger()->messages[3][0]);
$this->assertSame('Bootstrap with Closure', Yii::getLogger()->messages[4][0]);
$this->assertSame('Bootstrap with yii\base\Component', Yii::getLogger()->messages[0][1]);
$this->assertSame('Bootstrap with yiiunit\framework\base\BootstrapComponentMock::bootstrap()', Yii::getLogger()->messages[1][1]);
$this->assertSame('Loading module: moduleX', Yii::getLogger()->messages[2][1]);
$this->assertSame('Bootstrap with yii\base\Module', Yii::getLogger()->messages[3][1]);
$this->assertSame('Bootstrap with Closure', Yii::getLogger()->messages[4][1]);
}
}
class DispatcherMock extends Dispatcher
{
}
class BootstrapComponentMock extends Component implements BootstrapInterface
{
public function bootstrap($app)

46
tests/framework/db/ConnectionTest.php

@ -7,6 +7,7 @@
namespace yiiunit\framework\db;
use Yii;
use yii\db\Connection;
use yii\db\Transaction;
@ -235,51 +236,66 @@ abstract class ConnectionTest extends DatabaseTestCase
$connection->enableLogging = true;
$connection->enableProfiling = true;
\Yii::getLogger()->messages = [];
Yii::getLogger()->messages = [];
Yii::getProfiler()->messages = [];
$connection->createCommand()->createTable('qlog1', ['id' => 'pk'])->execute();
$this->assertCount(3, \Yii::getLogger()->messages);
$this->assertCount(1, Yii::getLogger()->messages);
$this->assertCount(1, Yii::getProfiler()->messages);
$this->assertNotNull($connection->getTableSchema('qlog1', true));
\Yii::getLogger()->messages = [];
Yii::getLogger()->messages = [];
Yii::getProfiler()->messages = [];
$connection->createCommand('SELECT * FROM qlog1')->queryAll();
$this->assertCount(3, \Yii::getLogger()->messages);
$this->assertCount(1, Yii::getLogger()->messages);
$this->assertCount(1, Yii::getProfiler()->messages);
// profiling only
$connection->enableLogging = false;
$connection->enableProfiling = true;
\Yii::getLogger()->messages = [];
Yii::getLogger()->messages = [];
Yii::getProfiler()->messages = [];
$connection->createCommand()->createTable('qlog2', ['id' => 'pk'])->execute();
$this->assertCount(2, \Yii::getLogger()->messages);
$this->assertCount(0, Yii::getLogger()->messages);
$this->assertCount(1, Yii::getProfiler()->messages);
$this->assertNotNull($connection->getTableSchema('qlog2', true));
\Yii::getLogger()->messages = [];
Yii::getLogger()->messages = [];
Yii::getProfiler()->messages = [];
$connection->createCommand('SELECT * FROM qlog2')->queryAll();
$this->assertCount(2, \Yii::getLogger()->messages);
$this->assertCount(0, Yii::getLogger()->messages);
$this->assertCount(1, Yii::getProfiler()->messages);
// logging only
$connection->enableLogging = true;
$connection->enableProfiling = false;
\Yii::getLogger()->messages = [];
Yii::getLogger()->messages = [];
Yii::getProfiler()->messages = [];
$connection->createCommand()->createTable('qlog3', ['id' => 'pk'])->execute();
$this->assertCount(1, \Yii::getLogger()->messages);
$this->assertCount(1, Yii::getLogger()->messages);
$this->assertCount(0, Yii::getProfiler()->messages);
$this->assertNotNull($connection->getTableSchema('qlog3', true));
\Yii::getLogger()->messages = [];
Yii::getLogger()->messages = [];
Yii::getProfiler()->messages = [];
$connection->createCommand('SELECT * FROM qlog3')->queryAll();
$this->assertCount(1, \Yii::getLogger()->messages);
$this->assertCount(1, Yii::getLogger()->messages);
$this->assertCount(0, Yii::getProfiler()->messages);
// disabled
$connection->enableLogging = false;
$connection->enableProfiling = false;
\Yii::getLogger()->messages = [];
Yii::getLogger()->messages = [];
Yii::getProfiler()->messages = [];
$connection->createCommand()->createTable('qlog4', ['id' => 'pk'])->execute();
$this->assertNotNull($connection->getTableSchema('qlog4', true));
$this->assertCount(0, \Yii::getLogger()->messages);
$this->assertCount(0, Yii::getLogger()->messages);
$this->assertCount(0, Yii::getProfiler()->messages);
$connection->createCommand('SELECT * FROM qlog4')->queryAll();
$this->assertCount(0, \Yii::getLogger()->messages);
$this->assertCount(0, Yii::getLogger()->messages);
$this->assertCount(0, Yii::getProfiler()->messages);
}
public function testExceptionContainsRawQuery()

2
tests/framework/i18n/I18NTest.php

@ -244,7 +244,7 @@ class I18NTest extends TestCase
$filter = function ($array) {
// Ensures that error message is related to PhpMessageSource
$className = $this->getMessageSourceClass();
return substr_compare($array[2], $className, 0, strlen($className)) === 0;
return substr_compare($array[2]['category'], $className, 0, strlen($className)) === 0;
};
$this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'en-GB'));

54
tests/framework/log/DbTargetTest.php

@ -7,10 +7,12 @@
namespace yiiunit\framework\log;
use Psr\Log\LogLevel;
use Yii;
use yii\console\ExitCode;
use yii\db\Connection;
use yii\db\Query;
use yii\log\Logger;
use yii\log\DbTarget;
use yiiunit\framework\console\controllers\EchoMigrateController;
use yiiunit\TestCase;
@ -39,25 +41,25 @@ abstract class DbTargetTest extends TestCase
'controllerMap' => [
'migrate' => EchoMigrateController::class,
],
'components' => [
'db' => static::getConnection(),
'log' => [
'targets' => [
'db' => [
'class' => 'yii\log\DbTarget',
'levels' => ['warning'],
'logTable' => self::$logTable,
],
'logger' => [
'targets' => [
'db' => [
'class' => DbTarget::class,
'levels' => [LogLevel::WARNING],
'logTable' => self::$logTable,
],
],
],
'components' => [
'db' => static::getConnection(),
],
]);
}
ob_start();
$result = Yii::$app->runAction($route, $params);
echo 'Result is ' . $result;
if ($result !== \yii\console\Controller::EXIT_CODE_NORMAL) {
if ($result !== ExitCode::OK) {
ob_end_flush();
} else {
ob_end_clean();
@ -126,11 +128,13 @@ abstract class DbTargetTest extends TestCase
// forming message data manually in order to set time
$messsageData = [
LogLevel::WARNING,
'test',
Logger::LEVEL_WARNING,
'test',
$time,
[],
[
'category' => 'test',
'time' => $time,
'trace' => [],
]
];
$logger->messages[] = $messsageData;
@ -138,7 +142,7 @@ abstract class DbTargetTest extends TestCase
$query = (new Query())->select('log_time')->from(self::$logTable)->where(['category' => 'test']);
$loggedTime = $query->createCommand(self::getConnection())->queryScalar();
static::assertEquals($time, $loggedTime);
$this->assertEquals($time, $loggedTime);
}
public function testTransactionRollBack()
@ -149,11 +153,13 @@ abstract class DbTargetTest extends TestCase
$tx = $db->beginTransaction();
$messsageData = [
LogLevel::WARNING,
'test',
Logger::LEVEL_WARNING,
'test',
time(),
[],
[
'category' => 'test',
'time' => time(),
'trace' => [],
]
];
$logger->messages[] = $messsageData;
@ -162,12 +168,14 @@ abstract class DbTargetTest extends TestCase
// current db connection should still have a transaction
$this->assertNotNull($db->transaction);
// log db connection should not have transaction
$this->assertNull(Yii::$app->log->targets['db']->db->transaction);
$this->assertNull(Yii::getLogger()->targets['db']->db->transaction);
$tx->rollBack();
$query = (new Query())->select('COUNT(*)')->from(self::$logTable)->where(['category' => 'test', 'message' => 'test']);
$count = $query->createCommand($db)->queryScalar();
$count = (new Query())
->from(self::$logTable)
->where(['category' => 'test', 'message' => 'test'])
->count();
static::assertEquals(1, $count);
}
}

266
tests/framework/log/DispatcherTest.php

@ -1,266 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\log {
function microtime($get_as_float)
{
if (\yiiunit\framework\log\DispatcherTest::$microtimeIsMocked) {
return \yiiunit\framework\log\DispatcherTest::microtime(func_get_args());
}
return \microtime($get_as_float);
}
}
namespace yiiunit\framework\log {
use Yii;
use yii\base\UserException;
use yii\log\Dispatcher;
use yii\log\Logger;
use yiiunit\TestCase;
/**
* @group log
*/
class DispatcherTest extends TestCase
{
/**
* @var Logger
*/
protected $logger;
/**
* @var Dispatcher
*/
protected $dispatcher;
/**
* @var bool
*/
public static $microtimeIsMocked = false;
/**
* Array of static functions
*
* @var array
*/
public static $functions = [];
protected function setUp()
{
static::$microtimeIsMocked = false;
$this->dispatcher = new Dispatcher();
$this->logger = new Logger();
}
public function testConfigureLogger()
{
$dispatcher = new Dispatcher();
$this->assertSame(Yii::getLogger(), $dispatcher->getLogger());
$logger = new Logger();
$dispatcher = new Dispatcher([
'logger' => $logger,
]);
$this->assertSame($logger, $dispatcher->getLogger());
$dispatcher = new Dispatcher([
'logger' => 'yii\log\Logger',
]);
$this->assertInstanceOf('yii\log\Logger', $dispatcher->getLogger());
$this->assertEquals(0, $dispatcher->getLogger()->traceLevel);
$dispatcher = new Dispatcher([
'logger' => [
'class' => 'yii\log\Logger',
'traceLevel' => 42,
],
]);
$this->assertInstanceOf('yii\log\Logger', $dispatcher->getLogger());
$this->assertEquals(42, $dispatcher->getLogger()->traceLevel);
}
/**
* @covers \yii\log\Dispatcher::setLogger()
*/
public function testSetLogger()
{
$this->dispatcher->setLogger($this->logger);
$this->assertSame($this->logger, $this->dispatcher->getLogger());
$this->dispatcher->setLogger('yii\log\Logger');
$this->assertInstanceOf('yii\log\Logger', $this->dispatcher->getLogger());
$this->assertEquals(0, $this->dispatcher->getLogger()->traceLevel);
$this->dispatcher->setLogger([
'class' => 'yii\log\Logger',
'traceLevel' => 42,
]);
$this->assertInstanceOf('yii\log\Logger', $this->dispatcher->getLogger());
$this->assertEquals(42, $this->dispatcher->getLogger()->traceLevel);
}
/**
* @covers \yii\log\Dispatcher::getTraceLevel()
*/
public function testGetTraceLevel()
{
$this->logger->traceLevel = 123;
$this->dispatcher->setLogger($this->logger);
$this->assertEquals(123, $this->dispatcher->getTraceLevel());
}
/**
* @covers \yii\log\Dispatcher::setTraceLevel()
*/
public function testSetTraceLevel()
{
$this->dispatcher->setLogger($this->logger);
$this->dispatcher->setTraceLevel(123);
$this->assertEquals(123, $this->logger->traceLevel);
}
/**
* @covers \yii\log\Dispatcher::getFlushInterval()
*/
public function testGetFlushInterval()
{
$this->logger->flushInterval = 99;
$this->dispatcher->setLogger($this->logger);
$this->assertEquals(99, $this->dispatcher->getFlushInterval());
}
/**
* @covers \yii\log\Dispatcher::setFlushInterval()
*/
public function testSetFlushInterval()
{
$this->dispatcher->setLogger($this->logger);
$this->dispatcher->setFlushInterval(99);
$this->assertEquals(99, $this->logger->flushInterval);
}
/**
* @covers \yii\log\Dispatcher::dispatch()
*/
public function testDispatchWithDisabledTarget()
{
$target = $this->getMockBuilder('yii\\log\\Target')
->setMethods(['collect'])
->getMockForAbstractClass();
$target->expects($this->never())->method($this->anything());
$target->enabled = false;
$dispatcher = new Dispatcher(['targets' => ['fakeTarget' => $target]]);
$dispatcher->dispatch('messages', true);
}
/**
* @covers \yii\log\Dispatcher::dispatch()
*/
public function testDispatchWithSuccessTargetCollect()
{
$target = $this->getMockBuilder('yii\\log\\Target')
->setMethods(['collect'])
->getMockForAbstractClass();
$target->expects($this->once())
->method('collect')
->with(
$this->equalTo('messages'),
$this->equalTo(true)
);
$dispatcher = new Dispatcher(['targets' => ['fakeTarget' => $target]]);
$dispatcher->dispatch('messages', true);
}
/**
* @covers \yii\log\Dispatcher::dispatch()
*/
public function testDispatchWithFakeTarget2ThrowExceptionWhenCollect()
{
static::$microtimeIsMocked = true;
$target1 = $this->getMockBuilder('yii\\log\\Target')
->setMethods(['collect'])
->getMockForAbstractClass();
$target2 = $this->getMockBuilder('yii\\log\\Target')
->setMethods(['collect'])
->getMockForAbstractClass();
$target1->expects($this->exactly(2))
->method('collect')
->withConsecutive(
[$this->equalTo('messages'), $this->equalTo(true)],
[
[[
'Unable to send log via ' . get_class($target1) . ': Exception: some error',
Logger::LEVEL_WARNING,
'yii\log\Dispatcher::dispatch',
'time data',
[],
]],
true,
]
);
$target2->expects($this->once())
->method('collect')
->with(
$this->equalTo('messages'),
$this->equalTo(true)
)->will($this->throwException(new UserException('some error')));
$dispatcher = new Dispatcher(['targets' => ['fakeTarget1' => $target1, 'fakeTarget2' => $target2]]);
static::$functions['microtime'] = function ($arguments) {
$this->assertEquals([true], $arguments);
return 'time data';
};
$dispatcher->dispatch('messages', true);
}
/**
* @covers \yii\log\Dispatcher::init()
*/
public function testInitWithCreateTargetObject()
{
$dispatcher = new Dispatcher(
[
'targets' => [
'syslog' => [
'class' => 'yii\log\SyslogTarget',
],
],
]
);
$this->assertEquals($dispatcher->targets['syslog'], Yii::createObject('yii\log\SyslogTarget'));
}
/**
* @param $name
* @param $arguments
* @return mixed
*/
public static function __callStatic($name, $arguments)
{
if (isset(static::$functions[$name]) && is_callable(static::$functions[$name])) {
$arguments = isset($arguments[0]) ? $arguments[0] : $arguments;
return forward_static_call(static::$functions[$name], $arguments);
}
static::fail("Function '$name' has not implemented yet!");
}
}
}

2
tests/framework/log/EmailTargetTest.php

@ -17,7 +17,7 @@ use yiiunit\TestCase;
class EmailTargetTest extends TestCase
{
/**
* @var PHPUnit_Framework_MockObject_MockObject
* @var \PHPUnit_Framework_MockObject_MockObject|\yii\mail\BaseMailer
*/
protected $mailer;

19
tests/framework/log/FileTargetTest.php

@ -7,9 +7,10 @@
namespace yiiunit\framework\log;
use Psr\Log\LogLevel;
use Yii;
use yii\helpers\FileHelper;
use yii\log\Dispatcher;
use yii\log\FileTarget;
use yii\log\Logger;
use yiiunit\TestCase;
@ -41,14 +42,12 @@ class FileTargetTest extends TestCase
FileHelper::removeDirectory(dirname($logFile));
mkdir(dirname($logFile), 0777, true);
$logger = new Logger();
$dispatcher = new Dispatcher([
'logger' => $logger,
$logger = new Logger([
'targets' => [
'file' => [
'class' => 'yii\log\FileTarget',
'class' => FileTarget::class,
'logFile' => $logFile,
'levels' => ['warning'],
'levels' => [LogLevel::WARNING],
'maxFileSize' => 1024, // 1 MB
'maxLogFiles' => 1, // one file for rotation and one normal log file
'logVars' => [],
@ -59,7 +58,7 @@ class FileTargetTest extends TestCase
// one file
$logger->log(str_repeat('x', 1024), Logger::LEVEL_WARNING);
$logger->log(LogLevel::WARNING, str_repeat('x', 1024));
$logger->flush(true);
clearstatcache();
@ -72,13 +71,13 @@ class FileTargetTest extends TestCase
// exceed max size
for ($i = 0; $i < 1024; $i++) {
$logger->log(str_repeat('x', 1024), Logger::LEVEL_WARNING);
$logger->log(LogLevel::WARNING, str_repeat('x', 1024));
}
$logger->flush(true);
// first rotate
$logger->log(str_repeat('x', 1024), Logger::LEVEL_WARNING);
$logger->log(LogLevel::WARNING, str_repeat('x', 1024));
$logger->flush(true);
clearstatcache();
@ -92,7 +91,7 @@ class FileTargetTest extends TestCase
// second rotate
for ($i = 0; $i < 1024; $i++) {
$logger->log(str_repeat('x', 1024), Logger::LEVEL_WARNING);
$logger->log(LogLevel::WARNING, str_repeat('x', 1024));
}
$logger->flush(true);

175
tests/framework/log/LoggerDispatchingTest.php

@ -0,0 +1,175 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\log {
function microtime($get_as_float)
{
if (\yiiunit\framework\log\LoggerDispatchingTest::$microtimeIsMocked) {
return \yiiunit\framework\log\LoggerDispatchingTest::microtime(func_get_args());
}
return \microtime($get_as_float);
}
}
namespace yiiunit\framework\log {
use Psr\Log\LogLevel;
use Yii;
use yii\base\UserException;
use yii\log\Logger;
use yiiunit\TestCase;
/**
* @group log
*/
class LoggerDispatchingTest extends TestCase
{
/**
* @var Logger
*/
protected $logger;
/**
* @var bool
*/
public static $microtimeIsMocked = false;
/**
* Array of static functions
*
* @var array
*/
public static $functions = [];
protected function setUp()
{
static::$microtimeIsMocked = false;
$this->logger = new Logger();
}
/**
* @covers \yii\log\Logger::dispatch()
*/
public function testDispatchWithDisabledTarget()
{
$target = $this->getMockBuilder('yii\\log\\Target')
->setMethods(['collect'])
->getMockForAbstractClass();
$target->expects($this->never())->method($this->anything());
$target->enabled = false;
$logger = new Logger(['targets' => ['fakeTarget' => $target]]);
$logger->messages = 'messages';
$logger->flush(true);
}
/**
* @covers \yii\log\Logger::dispatch()
*/
public function testDispatchWithSuccessTargetCollect()
{
$target = $this->getMockBuilder('yii\\log\\Target')
->setMethods(['collect'])
->getMockForAbstractClass();
$target->expects($this->once())
->method('collect')
->with(
$this->equalTo('messages'),
$this->equalTo(true)
);
$logger = new Logger(['targets' => ['fakeTarget' => $target]]);
$logger->messages = 'messages';
$logger->flush(true);
}
/**
* @covers \yii\log\Logger::dispatch()
*/
public function testDispatchWithFakeTarget2ThrowExceptionWhenCollect()
{
static::$microtimeIsMocked = true;
$target1 = $this->getMockBuilder('yii\\log\\Target')
->setMethods(['collect'])
->getMockForAbstractClass();
$target2 = $this->getMockBuilder('yii\\log\\Target')
->setMethods(['collect'])
->getMockForAbstractClass();
$target1->expects($this->exactly(2))
->method('collect')
->withConsecutive(
[$this->equalTo('messages'), $this->equalTo(true)],
[
[[
'Unable to send log via ' . get_class($target1) . ': Exception: some error',
LogLevel::WARNING,
'yii\log\Logger::dispatch',
'time data',
[],
]],
true,
]
);
$target2->expects($this->once())
->method('collect')
->with(
$this->equalTo('messages'),
$this->equalTo(true)
)->will($this->throwException(new UserException('some error')));
$logger = new Logger(['targets' => ['fakeTarget1' => $target1, 'fakeTarget2' => $target2]]);
static::$functions['microtime'] = function ($arguments) {
$this->assertEquals([true], $arguments);
return 'time data';
};
$logger->messages = 'messages';
$logger->flush(true);
}
/**
* @covers \yii\log\Logger::init()
*/
public function testInitWithCreateTargetObject()
{
$logger = new Logger(
[
'targets' => [
'syslog' => [
'class' => 'yii\log\SyslogTarget',
],
],
]
);
$this->assertEquals($logger->targets['syslog'], Yii::createObject('yii\log\SyslogTarget'));
}
/**
* @param $name
* @param $arguments
* @return mixed
*/
public static function __callStatic($name, $arguments)
{
if (isset(static::$functions[$name]) && is_callable(static::$functions[$name])) {
$arguments = isset($arguments[0]) ? $arguments[0] : $arguments;
return forward_static_call(static::$functions[$name], $arguments);
}
static::fail("Function '$name' has not implemented yet!");
}
}
}

465
tests/framework/log/LoggerTest.php

@ -7,8 +7,9 @@
namespace yiiunit\framework\log;
use yii\log\Dispatcher;
use Psr\Log\LogLevel;
use yii\log\Logger;
use yii\log\Target;
use yiiunit\TestCase;
/**
@ -17,19 +18,13 @@ use yiiunit\TestCase;
class LoggerTest extends TestCase
{
/**
* @var Logger
* @var Logger|\PHPUnit_Framework_MockObject_MockObject
*/
protected $logger;
/**
* @var Dispatcher|\PHPUnit_Framework_MockObject_MockObject
*/
protected $dispatcher;
protected function setUp()
{
$this->logger = new Logger();
$this->dispatcher = $this->getMockBuilder('yii\log\Dispatcher')
$this->logger = $this->getMockBuilder('yii\log\Logger')
->setMethods(['dispatch'])
->getMock();
}
@ -40,21 +35,21 @@ class LoggerTest extends TestCase
public function testLog()
{
$memory = memory_get_usage();
$this->logger->log('test1', Logger::LEVEL_INFO);
$this->logger->log(LogLevel::INFO, 'test1');
$this->assertCount(1, $this->logger->messages);
$this->assertEquals('test1', $this->logger->messages[0][0]);
$this->assertEquals(Logger::LEVEL_INFO, $this->logger->messages[0][1]);
$this->assertEquals('application', $this->logger->messages[0][2]);
$this->assertEquals([], $this->logger->messages[0][4]);
$this->assertGreaterThanOrEqual($memory, $this->logger->messages[0][5]);
$this->assertEquals(LogLevel::INFO, $this->logger->messages[0][0]);
$this->assertEquals('test1', $this->logger->messages[0][1]);
$this->assertEquals('application', $this->logger->messages[0][2]['category']);
$this->assertEquals([], $this->logger->messages[0][2]['trace']);
$this->assertGreaterThanOrEqual($memory, $this->logger->messages[0][2]['memory']);
$this->logger->log('test2', Logger::LEVEL_ERROR, 'category');
$this->logger->log(LogLevel::ERROR, 'test2', ['category' => 'category']);
$this->assertCount(2, $this->logger->messages);
$this->assertEquals('test2', $this->logger->messages[1][0]);
$this->assertEquals(Logger::LEVEL_ERROR, $this->logger->messages[1][1]);
$this->assertEquals('category', $this->logger->messages[1][2]);
$this->assertEquals([], $this->logger->messages[1][4]);
$this->assertGreaterThanOrEqual($memory, $this->logger->messages[1][5]);
$this->assertEquals(LogLevel::ERROR, $this->logger->messages[1][0]);
$this->assertEquals('test2', $this->logger->messages[1][1]);
$this->assertEquals('category', $this->logger->messages[1][2]['category']);
$this->assertEquals([], $this->logger->messages[1][2]['trace']);
$this->assertGreaterThanOrEqual($memory, $this->logger->messages[1][2]['memory']);
}
/**
@ -64,20 +59,20 @@ class LoggerTest extends TestCase
{
$memory = memory_get_usage();
$this->logger->traceLevel = 3;
$this->logger->log('test3', Logger::LEVEL_INFO);
$this->logger->log(LogLevel::INFO, 'test3');
$this->assertCount(1, $this->logger->messages);
$this->assertEquals('test3', $this->logger->messages[0][0]);
$this->assertEquals(Logger::LEVEL_INFO, $this->logger->messages[0][1]);
$this->assertEquals('application', $this->logger->messages[0][2]);
$this->assertEquals(LogLevel::INFO, $this->logger->messages[0][0]);
$this->assertEquals('test3', $this->logger->messages[0][1]);
$this->assertEquals('application', $this->logger->messages[0][2]['category']);
$this->assertEquals([
'file' => __FILE__,
'line' => 67,
'line' => 62,
'function' => 'log',
'class' => get_class($this->logger),
'class' => Logger::class,
'type' => '->',
], $this->logger->messages[0][4][0]);
$this->assertCount(3, $this->logger->messages[0][4]);
$this->assertGreaterThanOrEqual($memory, $this->logger->messages[0][5]);
], $this->logger->messages[0][2]['trace'][0]);
$this->assertCount(3, $this->logger->messages[0][2]['trace']);
$this->assertGreaterThanOrEqual($memory, $this->logger->messages[0][2]['memory']);
}
/**
@ -86,39 +81,24 @@ class LoggerTest extends TestCase
public function testLogWithFlush()
{
/* @var $logger Logger|\PHPUnit_Framework_MockObject_MockObject */
$logger = $this->getMockBuilder('yii\log\Logger')
$logger = $this->getMockBuilder(Logger::class)
->setMethods(['flush'])
->getMock();
$logger->flushInterval = 1;
$logger->expects($this->exactly(1))->method('flush');
$logger->log('test1', Logger::LEVEL_INFO);
}
/**
* @covers \yii\log\Logger::Flush()
*/
public function testFlushWithoutDispatcher()
{
$dispatcher = $this->getMockBuilder('\stdClass')->getMock();
$dispatcher->expects($this->never())->method($this->anything());
$this->logger->messages = ['anything'];
$this->logger->dispatcher = $dispatcher;
$this->logger->flush();
$this->assertEmpty($this->logger->messages);
$logger->log(LogLevel::INFO, 'test1');
}
/**
* @covers \yii\log\Logger::Flush()
*/
public function testFlushWithDispatcherAndDefaultParam()
public function testFlushWithDispatch()
{
$message = ['anything'];
$this->dispatcher->expects($this->once())
$this->logger->expects($this->once())
->method('dispatch')->with($this->equalTo($message), $this->equalTo(false));
$this->logger->messages = $message;
$this->logger->dispatcher = $this->dispatcher;
$this->logger->flush();
$this->assertEmpty($this->logger->messages);
}
@ -126,187 +106,18 @@ class LoggerTest extends TestCase
/**
* @covers \yii\log\Logger::Flush()
*/
public function testFlushWithDispatcherAndDefinedParam()
public function testFlushWithDispatchAndDefinedParam()
{
$message = ['anything'];
$this->dispatcher->expects($this->once())
$this->logger->expects($this->once())
->method('dispatch')->with($this->equalTo($message), $this->equalTo(true));
$this->logger->messages = $message;
$this->logger->dispatcher = $this->dispatcher;
$this->logger->flush(true);
$this->assertEmpty($this->logger->messages);
}
/**
* @covers \yii\log\Logger::getDbProfiling()
*/
public function testGetDbProfiling()
{
$timings = [
['duration' => 5],
['duration' => 15],
['duration' => 30],
];
/* @var $logger Logger|\PHPUnit_Framework_MockObject_MockObject */
$logger = $this->getMockBuilder('yii\log\Logger')
->setMethods(['getProfiling'])
->getMock();
$logger->method('getProfiling')->willReturn($timings);
$logger->expects($this->once())
->method('getProfiling')
->with($this->equalTo(['yii\db\Command::query', 'yii\db\Command::execute']));
$this->assertEquals([3, 50], $logger->getDbProfiling());
}
/**
* @covers \yii\log\Logger::calculateTimings()
*/
public function testCalculateTimingsWithEmptyMessages()
{
$this->assertEmpty($this->logger->calculateTimings([]));
}
/**
* @covers \yii\log\Logger::calculateTimings()
*/
public function testCalculateTimingsWithProfileNotBeginOrEnd()
{
$messages = [
['message0', Logger::LEVEL_ERROR, 'category', 'time', 'trace', 1048576],
['message1', Logger::LEVEL_INFO, 'category', 'time', 'trace', 1048576],
['message2', Logger::LEVEL_PROFILE, 'category', 'time', 'trace', 1048576],
['message3', Logger::LEVEL_TRACE, 'category', 'time', 'trace', 1048576],
['message4', Logger::LEVEL_WARNING, 'category', 'time', 'trace', 1048576],
[['message5', 'message6'], Logger::LEVEL_ERROR, 'category', 'time', 'trace', 1048576],
];
$this->assertEmpty($this->logger->calculateTimings($messages));
}
/**
* @covers \yii\log\Logger::calculateTimings()
*
* See https://github.com/yiisoft/yii2/issues/14264
*/
public function testCalculateTimingsWithProfileBeginEnd()
{
$messages = [
'anyKey' => ['token', Logger::LEVEL_PROFILE_BEGIN, 'category', 10, 'trace', 1048576],
'anyKey2' => ['token', Logger::LEVEL_PROFILE_END, 'category', 15, 'trace', 2097152],
];
$this->assertEquals([
[
'info' => 'token',
'category' => 'category',
'timestamp' => 10,
'trace' => 'trace',
'level' => 0,
'duration' => 5,
'memory' => 2097152,
'memoryDiff' => 1048576,
],
],
$this->logger->calculateTimings($messages)
);
$messages = [
'anyKey' => [['a', 'b'], Logger::LEVEL_PROFILE_BEGIN, 'category', 10, 'trace', 1048576],
'anyKey2' => [['a', 'b'], Logger::LEVEL_PROFILE_END, 'category', 15, 'trace', 2097152],
];
$this->assertEquals([
[
'info' => ['a', 'b'],
'category' => 'category',
'timestamp' => 10,
'trace' => 'trace',
'level' => 0,
'duration' => 5,
'memory' => 2097152,
'memoryDiff' => 1048576,
],
],
$this->logger->calculateTimings($messages)
);
}
/**
* @covers \yii\log\Logger::calculateTimings()
*/
public function testCalculateTimingsWithProfileBeginEndAndNestedLevels()
{
$messages = [
['firstLevel', Logger::LEVEL_PROFILE_BEGIN, 'firstLevelCategory', 10, 'firstTrace', 1048576],
['secondLevel', Logger::LEVEL_PROFILE_BEGIN, 'secondLevelCategory', 15, 'secondTrace', 2097152],
['secondLevel', Logger::LEVEL_PROFILE_END, 'secondLevelCategory', 55, 'secondTrace', 3145728],
['firstLevel', Logger::LEVEL_PROFILE_END, 'firstLevelCategory', 80, 'firstTrace', 4194304],
];
$this->assertEquals([
[
'info' => 'firstLevel',
'category' => 'firstLevelCategory',
'timestamp' => 10,
'trace' => 'firstTrace',
'level' => 0,
'duration' => 70,
'memory' => 4194304,
'memoryDiff' => 3145728,
],
[
'info' => 'secondLevel',
'category' => 'secondLevelCategory',
'timestamp' => 15,
'trace' => 'secondTrace',
'level' => 1,
'duration' => 40,
'memory' => 3145728,
'memoryDiff' => 1048576,
],
],
$this->logger->calculateTimings($messages)
);
}
/**
* See https://github.com/yiisoft/yii2/issues/14133
*
* @covers \yii\log\Logger::calculateTimings()
*/
public function testCalculateTimingsWithProfileBeginEndAndNestedMixedLevels()
{
$messages = [
['firstLevel', Logger::LEVEL_PROFILE_BEGIN, 'firstLevelCategory', 10, 'firstTrace', 1048576],
['secondLevel', Logger::LEVEL_PROFILE_BEGIN, 'secondLevelCategory', 15, 'secondTrace', 2097152],
['firstLevel', Logger::LEVEL_PROFILE_END, 'firstLevelCategory', 80, 'firstTrace', 4194304],
['secondLevel', Logger::LEVEL_PROFILE_END, 'secondLevelCategory', 55, 'secondTrace', 3145728],
];
$this->assertEquals([
[
'info' => 'firstLevel',
'category' => 'firstLevelCategory',
'timestamp' => 10,
'trace' => 'firstTrace',
'level' => 1,
'duration' => 70,
'memory' => 4194304,
'memoryDiff' => 3145728,
],
[
'info' => 'secondLevel',
'category' => 'secondLevelCategory',
'timestamp' => 15,
'trace' => 'secondTrace',
'level' => 0,
'duration' => 40,
'memory' => 3145728,
'memoryDiff' => 1048576,
],
],
$this->logger->calculateTimings($messages)
);
}
/**
* @covers \yii\log\Logger::getElapsedTime()
*/
public function testGetElapsedTime()
@ -326,173 +137,105 @@ class LoggerTest extends TestCase
*/
public function testGetLevelName()
{
$this->assertEquals('info', Logger::getLevelName(Logger::LEVEL_INFO));
$this->assertEquals('error', Logger::getLevelName(Logger::LEVEL_ERROR));
$this->assertEquals('warning', Logger::getLevelName(Logger::LEVEL_WARNING));
$this->assertEquals('trace', Logger::getLevelName(Logger::LEVEL_TRACE));
$this->assertEquals('profile', Logger::getLevelName(Logger::LEVEL_PROFILE));
$this->assertEquals('profile begin', Logger::getLevelName(Logger::LEVEL_PROFILE_BEGIN));
$this->assertEquals('profile end', Logger::getLevelName(Logger::LEVEL_PROFILE_END));
$this->assertEquals('info', Logger::getLevelName(LogLevel::INFO));
$this->assertEquals('error', Logger::getLevelName(LogLevel::ERROR));
$this->assertEquals('warning', Logger::getLevelName(LogLevel::WARNING));
$this->assertEquals('debug', Logger::getLevelName(LogLevel::DEBUG));
$this->assertEquals('emergency', Logger::getLevelName(LogLevel::EMERGENCY));
$this->assertEquals('alert', Logger::getLevelName(LogLevel::ALERT));
$this->assertEquals('critical', Logger::getLevelName(LogLevel::CRITICAL));
$this->assertEquals('unknown', Logger::getLevelName(0));
}
/**
* @covers \yii\log\Logger::getProfiling()
* @covers \yii\log\Logger::setTargets()
* @covers \yii\log\Logger::getTargets()
*/
public function testGetProfilingWithEmptyCategoriesAndExcludeCategories()
public function testSetupTarget()
{
$messages = ['anyData'];
$returnValue = 'return value';
/* @var $logger Logger|\PHPUnit_Framework_MockObject_MockObject */
$logger = $this->getMockBuilder('yii\log\Logger')
->setMethods(['calculateTimings'])
->getMock();
$logger = new Logger();
$logger->messages = $messages;
$logger->method('calculateTimings')->willReturn($returnValue);
$logger->expects($this->once())->method('calculateTimings')->with($this->equalTo($messages));
$this->assertEquals($returnValue, $logger->getProfiling());
}
$target = $this->getMockBuilder(Target::class)->getMockForAbstractClass();
$logger->setTargets([$target]);
/**
* @covers \yii\log\Logger::getProfiling()
*/
public function testGetProfilingWithNotEmptyCategoriesAndNotMatched()
{
$messages = ['anyData'];
$returnValue = [
$this->assertEquals([$target], $logger->getTargets());
$this->assertSame($target, $logger->getTargets()[0]);
$logger->setTargets([
[
'info' => 'token',
'category' => 'category',
'timestamp' => 10,
'trace' => 'trace',
'level' => 0,
'duration' => 5,
'class' => get_class($target),
],
];
/* @var $logger Logger|\PHPUnit_Framework_MockObject_MockObject */
$logger = $this->getMockBuilder('yii\log\Logger')
->setMethods(['calculateTimings'])
->getMock();
$logger->messages = $messages;
$logger->method('calculateTimings')->willReturn($returnValue);
$logger->expects($this->once())->method('calculateTimings')->with($this->equalTo($messages));
$this->assertEquals([], $logger->getProfiling(['not-matched-category']));
]);
$this->assertNotSame($target, $logger->getTargets()[0]);
$this->assertEquals(get_class($target), get_class($logger->getTargets()[0]));
}
/**
* @covers \yii\log\Logger::getProfiling()
* @depends testSetupTarget
*
* @covers \yii\log\Logger::addTarget()
*/
public function testGetProfilingWithNotEmptyCategoriesAndMatched()
public function testAddTarget()
{
$messages = ['anyData'];
$matchedByCategoryName = [
'info' => 'token',
'category' => 'category',
'timestamp' => 10,
'trace' => 'trace',
'level' => 0,
'duration' => 5,
];
$secondCategory = [
'info' => 'secondToken',
'category' => 'category2',
'timestamp' => 10,
'trace' => 'trace',
'level' => 0,
'duration' => 5,
];
$returnValue = [
'anyKey' => $matchedByCategoryName,
$secondCategory,
];
/*
* Matched by category name
*/
/* @var $logger Logger|\PHPUnit_Framework_MockObject_MockObject */
$logger = $this->getMockBuilder('yii\log\Logger')
->setMethods(['calculateTimings'])
->getMock();
$logger = new Logger();
$logger->messages = $messages;
$logger->method('calculateTimings')->willReturn($returnValue);
$logger->expects($this->once())->method('calculateTimings')->with($this->equalTo($messages));
$this->assertEquals([$matchedByCategoryName], $logger->getProfiling(['category']));
$target = $this->getMockBuilder(Target::class)->getMockForAbstractClass();
$logger->setTargets([$target]);
/*
* Matched by prefix
*/
/* @var $logger Logger|\PHPUnit_Framework_MockObject_MockObject */
$logger = $this->getMockBuilder('yii\log\Logger')
->setMethods(['calculateTimings'])
->getMock();
$namedTarget = $this->getMockBuilder(Target::class)->getMockForAbstractClass();
$logger->addTarget($namedTarget, 'test-target');
$logger->messages = $messages;
$logger->method('calculateTimings')->willReturn($returnValue);
$logger->expects($this->once())->method('calculateTimings')->with($this->equalTo($messages));
$this->assertEquals([$matchedByCategoryName, $secondCategory], $logger->getProfiling(['category*']));
$targets = $logger->getTargets();
$this->assertCount(2, $targets);
$this->assertTrue(isset($targets['test-target']));
$this->assertSame($namedTarget, $targets['test-target']);
$namelessTarget = $this->getMockBuilder(Target::class)->getMockForAbstractClass();
$logger->addTarget($namelessTarget);
$targets = $logger->getTargets();
$this->assertCount(3, $targets);
$this->assertSame($namelessTarget, array_pop($targets));
}
/**
* @covers \yii\log\Logger::getProfiling()
* Data provider for [[testParseMessage()]]
* @return array test data.
*/
public function testGetProfilingWithNotEmptyCategoriesMatchedAndExcludeCategories()
public function dataProviderParseMessage()
{
$messages = ['anyData'];
$fistCategory = [
'info' => 'fistToken',
'category' => 'cat',
'timestamp' => 10,
'trace' => 'trace',
'level' => 0,
'duration' => 5,
];
$secondCategory = [
'info' => 'secondToken',
'category' => 'category2',
'timestamp' => 10,
'trace' => 'trace',
'level' => 0,
'duration' => 5,
];
$returnValue = [
$fistCategory,
$secondCategory,
return [
[
'no placeholder',
['foo' => 'some'],
'no placeholder',
],
[
'has {foo} placeholder',
['foo' => 'some'],
'has some placeholder',
],
[
'info' => 'anotherToken',
'category' => 'category3',
'timestamp' => 10,
'trace' => 'trace',
'level' => 0,
'duration' => 5,
'has {foo} placeholder',
[],
'has {foo} placeholder',
],
];
}
/*
* Exclude by category name
*/
/* @var $logger Logger|\PHPUnit_Framework_MockObject_MockObject */
$logger = $this->getMockBuilder('yii\log\Logger')
->setMethods(['calculateTimings'])
->getMock();
$logger->messages = $messages;
$logger->method('calculateTimings')->willReturn($returnValue);
$logger->expects($this->once())->method('calculateTimings')->with($this->equalTo($messages));
$this->assertEquals([$fistCategory, $secondCategory], $logger->getProfiling(['cat*'], ['category3']));
/*
* Exclude by category prefix
*/
/* @var $logger Logger|\PHPUnit_Framework_MockObject_MockObject */
$logger = $this->getMockBuilder('yii\log\Logger')
->setMethods(['calculateTimings'])
->getMock();
$logger->messages = $messages;
$logger->method('calculateTimings')->willReturn($returnValue);
$logger->expects($this->once())->method('calculateTimings')->with($this->equalTo($messages));
$this->assertEquals([$fistCategory], $logger->getProfiling(['cat*'], ['category*']));
/**
* @depends testLog
* @dataProvider dataProviderParseMessage
*
* @covers \yii\log\Logger::parseMessage()
*
* @param $message
* @param array $context
* @param $expected
*/
public function testParseMessage($message, array $context, $expected)
{
$this->logger->log(LogLevel::INFO, $message, $context);
[$level, $message, $context] = $this->logger->messages[0];
$this->assertEquals($expected, $message);
}
}

51
tests/framework/log/SyslogTargetTest.php

@ -26,8 +26,10 @@ namespace yii\log {
namespace yiiunit\framework\log {
use PHPUnit_Framework_MockObject_MockObject;
use Psr\Log\LogLevel;
use yii\helpers\VarDumper;
use yii\log\Logger;
use yii\log\SyslogTarget;
use yiiunit\TestCase;
/**
@ -54,7 +56,7 @@ namespace yiiunit\framework\log {
*/
protected function setUp()
{
$this->syslogTarget = $this->getMockBuilder('yii\\log\\SyslogTarget')
$this->syslogTarget = $this->getMockBuilder(SyslogTarget::class)
->setMethods(['getMessagePrefix'])
->getMock();
}
@ -68,15 +70,17 @@ namespace yiiunit\framework\log {
$options = LOG_ODELAY | LOG_PID;
$facility = 'facility string';
$messages = [
['info message', Logger::LEVEL_INFO],
['error message', Logger::LEVEL_ERROR],
['warning message', Logger::LEVEL_WARNING],
['trace message', Logger::LEVEL_TRACE],
['profile message', Logger::LEVEL_PROFILE],
['profile begin message', Logger::LEVEL_PROFILE_BEGIN],
['profile end message', Logger::LEVEL_PROFILE_END],
[LogLevel::INFO, 'info message'],
[LogLevel::ERROR, 'error message'],
[LogLevel::WARNING, 'warning message'],
[LogLevel::DEBUG, 'trace message'],
[LogLevel::NOTICE, 'notice message'],
[LogLevel::EMERGENCY, 'emergency message'],
[LogLevel::ALERT, 'alert message'],
];
$syslogTarget = $this->getMockBuilder('yii\\log\\SyslogTarget')
/* @var $syslogTarget SyslogTarget|PHPUnit_Framework_MockObject_MockObject */
$syslogTarget = $this->getMockBuilder(SyslogTarget::class)
->setMethods(['openlog', 'syslog', 'formatMessage', 'closelog'])
->getMock();
@ -120,9 +124,9 @@ namespace yiiunit\framework\log {
[$this->equalTo(LOG_ERR), $this->equalTo('formatted message 2')],
[$this->equalTo(LOG_WARNING), $this->equalTo('formatted message 3')],
[$this->equalTo(LOG_DEBUG), $this->equalTo('formatted message 4')],
[$this->equalTo(LOG_DEBUG), $this->equalTo('formatted message 5')],
[$this->equalTo(LOG_DEBUG), $this->equalTo('formatted message 6')],
[$this->equalTo(LOG_DEBUG), $this->equalTo('formatted message 7')]
[$this->equalTo(LOG_NOTICE), $this->equalTo('formatted message 5')],
[$this->equalTo(LOG_EMERG), $this->equalTo('formatted message 6')],
[$this->equalTo(LOG_ALERT), $this->equalTo('formatted message 7')]
);
$syslogTarget->expects($this->once())->method('closelog');
@ -166,7 +170,7 @@ namespace yiiunit\framework\log {
*/
public function testFormatMessageWhereTextIsString()
{
$message = ['text', Logger::LEVEL_INFO, 'category', 'timestamp'];
$message = [LogLevel::INFO, 'text', ['category' => 'category', 'time' => 'timestamp']];
$this->syslogTarget
->expects($this->once())
@ -184,7 +188,7 @@ namespace yiiunit\framework\log {
public function testFormatMessageWhereTextIsException()
{
$exception = new \Exception('exception text');
$message = [$exception, Logger::LEVEL_INFO, 'category', 'timestamp'];
$message = [LogLevel::INFO, $exception, ['category' => 'category', 'time' => 'timestamp']];
$this->syslogTarget
->expects($this->once())
@ -195,24 +199,5 @@ namespace yiiunit\framework\log {
$result = $this->syslogTarget->formatMessage($message);
$this->assertEquals('some prefix[info][category] ' . (string) $exception, $result);
}
/**
* @covers \yii\log\SyslogTarget::formatMessage()
*/
public function testFormatMessageWhereTextIsNotStringAndNotThrowable()
{
$text = new \stdClass();
$text->var = 'some text';
$message = [$text, Logger::LEVEL_ERROR, 'category', 'timestamp'];
$this->syslogTarget
->expects($this->once())
->method('getMessagePrefix')
->with($this->equalTo($message))
->willReturn('some prefix');
$result = $this->syslogTarget->formatMessage($message);
$this->assertEquals('some prefix[error][category] ' . VarDumper::export($text), $result);
}
}
}

76
tests/framework/log/TargetTest.php

@ -7,7 +7,7 @@
namespace yiiunit\framework\log;
use yii\log\Dispatcher;
use Psr\Log\LogLevel;
use yii\log\Logger;
use yii\log\Target;
use yiiunit\TestCase;
@ -24,15 +24,15 @@ class TargetTest extends TestCase
return [
[[], ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']],
[['levels' => 0], ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']],
[['levels' => []], ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']],
[
['levels' => Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_ERROR | Logger::LEVEL_TRACE],
['levels' => [LogLevel::INFO, LogLevel::WARNING, LogLevel::ERROR, LogLevel::DEBUG]],
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],
],
[['levels' => ['error']], ['B', 'G', 'H']],
[['levels' => Logger::LEVEL_ERROR], ['B', 'G', 'H']],
[['levels' => [LogLevel::ERROR]], ['B', 'G', 'H']],
[['levels' => ['error', 'warning']], ['B', 'C', 'G', 'H']],
[['levels' => Logger::LEVEL_ERROR | Logger::LEVEL_WARNING], ['B', 'C', 'G', 'H']],
[['levels' => [LogLevel::ERROR, LogLevel::WARNING]], ['B', 'C', 'G', 'H']],
[['categories' => ['application']], ['A', 'B', 'C', 'D', 'E']],
[['categories' => ['application*']], ['A', 'B', 'C', 'D', 'E', 'F']],
@ -43,9 +43,9 @@ class TargetTest extends TestCase
[['categories' => ['application.*', 'yii.db.*']], ['F', 'G', 'H']],
[['categories' => ['application.*', 'yii.db.*'], 'except' => ['yii.db.Command.*']], ['F', 'G']],
[['categories' => ['application', 'yii.db.*'], 'levels' => Logger::LEVEL_ERROR], ['B', 'G', 'H']],
[['categories' => ['application'], 'levels' => Logger::LEVEL_ERROR], ['B']],
[['categories' => ['application'], 'levels' => Logger::LEVEL_ERROR | Logger::LEVEL_WARNING], ['B', 'C']],
[['categories' => ['application', 'yii.db.*'], 'levels' => [LogLevel::ERROR]], ['B', 'G', 'H']],
[['categories' => ['application'], 'levels' => [LogLevel::ERROR]], ['B']],
[['categories' => ['application'], 'levels' => [LogLevel::ERROR, LogLevel::WARNING]], ['B', 'C']],
];
}
@ -56,25 +56,23 @@ class TargetTest extends TestCase
{
static::$messages = [];
$logger = new Logger();
$dispatcher = new Dispatcher([
'logger' => $logger,
$logger = new Logger([
'targets' => [new TestTarget(array_merge($filter, ['logVars' => []]))],
'flushInterval' => 1,
]);
$logger->log('testA', Logger::LEVEL_INFO);
$logger->log('testB', Logger::LEVEL_ERROR);
$logger->log('testC', Logger::LEVEL_WARNING);
$logger->log('testD', Logger::LEVEL_TRACE);
$logger->log('testE', Logger::LEVEL_INFO, 'application');
$logger->log('testF', Logger::LEVEL_INFO, 'application.components.Test');
$logger->log('testG', Logger::LEVEL_ERROR, 'yii.db.Command');
$logger->log('testH', Logger::LEVEL_ERROR, 'yii.db.Command.whatever');
$logger->log(LogLevel::INFO, 'testA');
$logger->log(LogLevel::ERROR, 'testB');
$logger->log(LogLevel::WARNING, 'testC');
$logger->log(LogLevel::DEBUG, 'testD');
$logger->log(LogLevel::INFO, 'testE', ['category' => 'application']);
$logger->log(LogLevel::INFO, 'testF', ['category' => 'application.components.Test']);
$logger->log(LogLevel::ERROR, 'testG', ['category' => 'yii.db.Command']);
$logger->log(LogLevel::ERROR, 'testH', ['category' => 'yii.db.Command.whatever']);
$this->assertEquals(count($expected), count(static::$messages));
$i = 0;
foreach ($expected as $e) {
$this->assertEquals('test' . $e, static::$messages[$i++][0]);
$this->assertEquals('test' . $e, static::$messages[$i++][1]);
}
}
@ -125,44 +123,6 @@ class TargetTest extends TestCase
$this->assertNotContains('E_b', $context);
$this->assertNotContains('E_c', $context);
}
/**
* @covers \yii\log\Target::setLevels()
* @covers \yii\log\Target::getLevels()
*/
public function testSetupLevelsThroughArray()
{
$target = $this->getMockForAbstractClass('yii\\log\\Target');
$target->setLevels(['info', 'error']);
$this->assertEquals(Logger::LEVEL_INFO | Logger::LEVEL_ERROR, $target->getLevels());
$target->setLevels(['trace']);
$this->assertEquals(Logger::LEVEL_TRACE, $target->getLevels());
$this->expectException('yii\\base\\InvalidConfigException');
$this->expectExceptionMessage('Unrecognized level: unknown level');
$target->setLevels(['info', 'unknown level']);
}
/**
* @covers \yii\log\Target::setLevels()
* @covers \yii\log\Target::getLevels()
*/
public function testSetupLevelsThroughBitmap()
{
$target = $this->getMockForAbstractClass('yii\\log\\Target');
$target->setLevels(Logger::LEVEL_INFO | Logger::LEVEL_WARNING);
$this->assertEquals(Logger::LEVEL_INFO | Logger::LEVEL_WARNING, $target->getLevels());
$target->setLevels(Logger::LEVEL_TRACE);
$this->assertEquals(Logger::LEVEL_TRACE, $target->getLevels());
$this->expectException('yii\\base\\InvalidConfigException');
$this->expectExceptionMessage('Incorrect 128 value');
$target->setLevels(128);
}
}
class TestTarget extends Target

69
tests/framework/profile/LogTargetTest.php

@ -0,0 +1,69 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\profile;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Yii;
use yii\profile\LogTarget;
use yiiunit\TestCase;
class LogTargetTest extends TestCase
{
/**
* @covers \yii\profile\LogTarget::setLogger()
* @covers \yii\profile\LogTarget::getLogger()
*/
public function testSetupLogger()
{
$target = new LogTarget();
$logger = new NullLogger();
$target->setLogger($logger);
$this->assertSame($logger, $target->getLogger());
$target->setLogger(['class' => NullLogger::class]);
$this->assertNotSame($logger, $target->getLogger());
$this->assertTrue($target->getLogger() instanceof NullLogger);
$target->setLogger(null);
$this->assertSame(Yii::getLogger(), $target->getLogger());
}
/**
* @depends testSetupLogger
*
* @covers \yii\profile\LogTarget::export()
*/
public function testExport()
{
/* @var $logger LoggerInterface|\PHPUnit_Framework_MockObject_MockObject */
$logger = $this->getMockBuilder(LoggerInterface::class)
->setMethods([
'log'
])
->getMockForAbstractClass();
$target = new LogTarget();
$target->setLogger($logger);
$target->logLevel = 'test-level';
$logger->expects($this->once())
->method('log')
->with($this->equalTo($target->logLevel), $this->equalTo('test-token'));
$target->export([
[
'category' => 'test',
'token' => 'test-token',
'beginTime' => 123,
'endTime' => 321,
],
]);
}
}

122
tests/framework/profile/ProfilerTest.php

@ -0,0 +1,122 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\profile;
use yii\profile\LogTarget;
use yii\profile\Profiler;
use yii\profile\Target;
use yiiunit\TestCase;
/**
* @group profile
*/
class ProfilerTest extends TestCase
{
/**
* @covers \yii\profile\Profiler::setTargets()
* @covers \yii\profile\Profiler::getTargets()
*/
public function testSetupTarget()
{
$profiler = new Profiler();
$target = new LogTarget();
$profiler->setTargets([$target]);
$this->assertEquals([$target], $profiler->getTargets());
$this->assertSame($target, $profiler->getTargets()[0]);
$profiler->setTargets([
[
'class' => LogTarget::class,
'logLevel' => 'test',
],
]);
$target = $profiler->getTargets()[0];
$this->assertTrue($target instanceof LogTarget);
$this->assertEquals('test', $target->logLevel);
}
/**
* @depends testSetupTarget
*
* @covers \yii\profile\Profiler::addTarget()
*/
public function testAddTarget()
{
$profiler = new Profiler();
$target = $this->getMockBuilder(Target::class)->getMockForAbstractClass();
$profiler->setTargets([$target]);
$namedTarget = $this->getMockBuilder(Target::class)->getMockForAbstractClass();
$profiler->addTarget($namedTarget, 'test-target');
$targets = $profiler->getTargets();
$this->assertCount(2, $targets);
$this->assertTrue(isset($targets['test-target']));
$this->assertSame($namedTarget, $targets['test-target']);
$namelessTarget = $this->getMockBuilder(Target::class)->getMockForAbstractClass();
$profiler->addTarget($namelessTarget);
$targets = $profiler->getTargets();
$this->assertCount(3, $targets);
$this->assertSame($namelessTarget, array_pop($targets));
}
public function testEnabled()
{
$profiler = new Profiler();
$profiler->enabled = false;
$profiler->begin('test');
$profiler->end('test');
$this->assertEmpty($profiler->messages);
$profiler->enabled = true;
$profiler->begin('test');
$profiler->end('test');
$this->assertCount(1, $profiler->messages);
}
/**
* @covers \yii\profile\Profiler::flush()
*/
public function testFlushWithDispatch()
{
/* @var $profiler Profiler|\PHPUnit_Framework_MockObject_MockObject */
$profiler = $this->getMockBuilder(Profiler::class)
->setMethods(['dispatch'])
->getMock();
$message = ['anything'];
$profiler->expects($this->once())
->method('dispatch')
->with($this->equalTo($message));
$profiler->messages = $message;
$profiler->flush();
$this->assertEmpty($profiler->messages);
}
public function testNestedMessages()
{
$profiler = new Profiler();
$profiler->begin('test');
$profiler->begin('test');
$profiler->end('test');
$profiler->end('test');
$this->assertCount(2, $profiler->messages);
}
}

97
tests/framework/profile/TargetTest.php

@ -0,0 +1,97 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\profile;
use yii\profile\Target;
use yiiunit\TestCase;
/**
* @group profile
*/
class TargetTest extends TestCase
{
/**
* Data provider for [[testFilterMessages()]]
* @return array test data
*/
public function dataProviderFilterMessages()
{
return [
[
[['category' => 'foo']],
[],
[],
[['category' => 'foo']],
],
[
[['category' => 'foo']],
['foo'],
[],
[['category' => 'foo']],
],
[
[['category' => 'foo']],
['some'],
[],
[],
],
[
[['category' => 'foo']],
[],
['foo'],
[],
],
[
[['category' => 'foo']],
[],
['some'],
[['category' => 'foo']],
],
];
}
/**
* @dataProvider dataProviderFilterMessages
*
* @covers \yii\profile\Target::filterMessages()
*
* @param array $messages
* @param array $categories
* @param array $except
* @param array $expected
*/
public function testFilterMessages(array $messages, array $categories, array $except, array $expected)
{
/* @var $target Target|\PHPUnit_Framework_MockObject_MockObject */
$target = $this->getMockBuilder(Target::class)->getMockForAbstractClass();
$target->categories = $categories;
$target->except = $except;
$this->assertEquals($expected, $this->invokeMethod($target, 'filterMessages', [$messages]));
}
/**
* @depends testFilterMessages
*/
public function testEnabled()
{
/* @var $target Target|\PHPUnit_Framework_MockObject_MockObject */
$target = $this->getMockBuilder(Target::class)
->setMethods(['export'])
->getMock();
$target->expects($this->exactly(0))->method('export');
$target->enabled = false;
$target->collect([['category' => 'foo']]);
$target = $this->getMockBuilder(Target::class)
->setMethods(['export'])
->getMock();
$target->expects($this->exactly(1))->method('export');
$target->enabled = true;
$target->collect([['category' => 'foo']]);
}
}
Loading…
Cancel
Save