Paul Klimov
7 years ago
committed by
GitHub
58 changed files with 1951 additions and 1523 deletions
@ -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. |
@ -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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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(); |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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!"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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!"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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, |
||||||
|
], |
||||||
|
]); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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…
Reference in new issue