feature #10466 [Debug] add a screaming mode to ErrorHandler (nicolas-grekas)
This PR was merged into the 2.5-dev branch.
Discussion
----------
[Debug] add a screaming mode to ErrorHandler
| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | none
| License | MIT
| Doc PR | none
Collect and display silenced PHP errors in the debug toolbar.
Commits
-------
5cc817d
[Debug] add a screaming mode to ErrorHandler
This commit is contained in:
commit
71dc07ce08
@ -32,5 +32,12 @@
|
|||||||
<argument>deprecation</argument>
|
<argument>deprecation</argument>
|
||||||
<argument type="service" id="logger" on-invalid="null" />
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="debug.scream_logger_listener" class="%debug.errors_logger_listener.class%">
|
||||||
|
<tag name="kernel.event_subscriber" />
|
||||||
|
<tag name="monolog.logger" channel="scream" />
|
||||||
|
<argument>scream</argument>
|
||||||
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
|
</service>
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
@ -3,16 +3,16 @@
|
|||||||
{% import _self as logger %}
|
{% import _self as logger %}
|
||||||
|
|
||||||
{% block toolbar %}
|
{% block toolbar %}
|
||||||
{% if collector.counterrors or collector.countdeprecations %}
|
{% if collector.counterrors or collector.countdeprecations or collector.countscreams %}
|
||||||
{% set icon %}
|
{% set icon %}
|
||||||
<img width="15" height="28" alt="Logs" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAcCAYAAABoMT8aAAAA4klEQVQ4y2P4//8/AyWYYXgYwOPp6Xnc3t7+P7EYpB6k7+zZs2ADNEjRjIwDAgKWgAywIUfz8+fPVzg7O/8AGeCATQEQnAfi/SAah/wcV1dXvAYUgORANA75ehcXl+/4DHAABRIe+ZrhbgAhTHsDiEgHBA0glA6GfSDiw5mZma+A+sphBlhVVFQ88vHx+Xfu3Ll7QP5haOjjwtuAuGHv3r3NIMNABqh8+/atsaur666vr+9XUlwSHx//AGQANxCbAnEWyGQicRMQ9wBxIQM0qjiBWAFqkB00/glhayBWHwb1AgB38EJsUtxtWwAAAABJRU5ErkJggg==" />
|
<img width="15" height="28" alt="Logs" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAcCAYAAABoMT8aAAAA4klEQVQ4y2P4//8/AyWYYXgYwOPp6Xnc3t7+P7EYpB6k7+zZs2ADNEjRjIwDAgKWgAywIUfz8+fPVzg7O/8AGeCATQEQnAfi/SAah/wcV1dXvAYUgORANA75ehcXl+/4DHAABRIe+ZrhbgAhTHsDiEgHBA0glA6GfSDiw5mZma+A+sphBlhVVFQ88vHx+Xfu3Ll7QP5haOjjwtuAuGHv3r3NIMNABqh8+/atsaur666vr+9XUlwSHx//AGQANxCbAnEWyGQicRMQ9wBxIQM0qjiBWAFqkB00/glhayBWHwb1AgB38EJsUtxtWwAAAABJRU5ErkJggg==" />
|
||||||
{% if collector.counterrors %}
|
{% if collector.counterrors %}
|
||||||
{% set status_color = "red" %}
|
{% set status_color = "red" %}
|
||||||
{% else %}
|
{% elseif collector.countdeprecations %}
|
||||||
{% set status_color = "yellow" %}
|
{% set status_color = "yellow" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% set error_count = collector.counterrors + collector.countdeprecations %}
|
{% set error_count = collector.counterrors + collector.countdeprecations + collector.countscreams %}
|
||||||
<span class="sf-toolbar-status sf-toolbar-status-{{ status_color }}">{{ error_count }}</span>
|
<span class="sf-toolbar-status{% if status_color is defined %}} sf-toolbar-status-{{ status_color }}{% endif %}">{{ error_count }}</span>
|
||||||
{% endset %}
|
{% endset %}
|
||||||
{% set text %}
|
{% set text %}
|
||||||
{% if collector.counterrors %}
|
{% if collector.counterrors %}
|
||||||
@ -27,6 +27,12 @@
|
|||||||
<span class="sf-toolbar-status sf-toolbar-status-yellow">{{ collector.countdeprecations }}</span>
|
<span class="sf-toolbar-status sf-toolbar-status-yellow">{{ collector.countdeprecations }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if collector.countscreams %}
|
||||||
|
<div class="sf-toolbar-info-piece">
|
||||||
|
<b>Silenced Errors</b>
|
||||||
|
<span class="sf-toolbar-status sf-toolbar-status">{{ collector.countscreams }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endset %}
|
{% endset %}
|
||||||
{% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %}
|
{% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -36,8 +42,8 @@
|
|||||||
<span class="label">
|
<span class="label">
|
||||||
<span class="icon"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAgCAYAAAAMq2gFAAABjElEQVRIx2MIDw+vd3R0/GFvb/+fGtjFxeVJSUmJ1f///5nv37/PAMMMzs7OVLMEhoODgy/k5+cHJCYmagAtZAJbRG1L0DEwxCYALeOgiUXbt2+/X1NT8xTEdnd3/wi0SI4mFgHBDCBeCLXoF5BtwkCEpvNAvB8JnydCTwgQR0It+g1kWxNjUQEQOyDhAiL0gNUiWWRDjEUOyMkUZsCoRaMWjVpEvEVkFkGjFmEUqgc+fvx4hVYWIReqzi9evKileaoDslnu3LkTNLQtGk3edLPIycnpL9Bge5pb1NXVdQNosDmGRcAm7F+QgKur6783b95cBQoeRGv1kII3QPOdAoZF8+fPP4PUqnx55syZVKCEI1rLh1hsAbWEZ8aMGaUoFoFcMG3atKdIjfSPISEhawICAlaQgwMDA1f6+/sfB5rzE2Sej4/PD3C7DkjoAHHVoUOHLpSVlX3w8vL6Sa34Alr6Z8WKFaCoMARZxAHEoFZ/HBD3A/FyIF4BxMvIxCC964F4G6hZDMTxQCwJAGWE8pur5kFDAAAAAElFTkSuQmCC" alt="Logger"></span>
|
<span class="icon"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAgCAYAAAAMq2gFAAABjElEQVRIx2MIDw+vd3R0/GFvb/+fGtjFxeVJSUmJ1f///5nv37/PAMMMzs7OVLMEhoODgy/k5+cHJCYmagAtZAJbRG1L0DEwxCYALeOgiUXbt2+/X1NT8xTEdnd3/wi0SI4mFgHBDCBeCLXoF5BtwkCEpvNAvB8JnydCTwgQR0It+g1kWxNjUQEQOyDhAiL0gNUiWWRDjEUOyMkUZsCoRaMWjVpEvEVkFkGjFmEUqgc+fvx4hVYWIReqzi9evKileaoDslnu3LkTNLQtGk3edLPIycnpL9Bge5pb1NXVdQNosDmGRcAm7F+QgKur6783b95cBQoeRGv1kII3QPOdAoZF8+fPP4PUqnx55syZVKCEI1rLh1hsAbWEZ8aMGaUoFoFcMG3atKdIjfSPISEhawICAlaQgwMDA1f6+/sfB5rzE2Sej4/PD3C7DkjoAHHVoUOHLpSVlX3w8vL6Sa34Alr6Z8WKFaCoMARZxAHEoFZ/HBD3A/FyIF4BxMvIxCC964F4G6hZDMTxQCwJAGWE8pur5kFDAAAAAElFTkSuQmCC" alt="Logger"></span>
|
||||||
<strong>Logs</strong>
|
<strong>Logs</strong>
|
||||||
{% if collector.counterrors or collector.countdeprecations %}
|
{% if collector.counterrors or collector.countdeprecations or collector.countscreams %}
|
||||||
{% set error_count = collector.counterrors + collector.countdeprecations %}
|
{% set error_count = collector.counterrors + collector.countdeprecations + collector.countscreams %}
|
||||||
<span class="count">
|
<span class="count">
|
||||||
<span>{{ error_count }}</span>
|
<span>{{ error_count }}</span>
|
||||||
</span>
|
</span>
|
||||||
@ -74,7 +80,7 @@
|
|||||||
{% if collector.logs %}
|
{% if collector.logs %}
|
||||||
<ul class="alt">
|
<ul class="alt">
|
||||||
{% for log in collector.logs if priority >= 0 and log.priority >= priority or priority < 0 and log.context.type|default(0) == priority %}
|
{% for log in collector.logs if priority >= 0 and log.priority >= priority or priority < 0 and log.context.type|default(0) == priority %}
|
||||||
<li class="{{ cycle(['odd', 'even'], loop.index) }}{% if log.priority >= 400 %} error{% elseif log.priority >= 300 %} warning{% endif %}">
|
<li class="{{ cycle(['odd', 'even'], loop.index) }}{% if log.priority >= 400 %} error{% elseif log.priority >= 300 %} warning{% endif %}{% if log.context.scream is defined %} scream{% endif %}">
|
||||||
{{ logger.display_message(loop.index, log) }}
|
{{ logger.display_message(loop.index, log) }}
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -275,6 +275,9 @@ ul.alt li.warning {
|
|||||||
background-color: #ffcc00;
|
background-color: #ffcc00;
|
||||||
margin-bottom: 1px;
|
margin-bottom: 1px;
|
||||||
}
|
}
|
||||||
|
ul.alt li.scream, ul.alt li.scream strong {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
ul.sf-call-stack li {
|
ul.sf-call-stack li {
|
||||||
text-size: small;
|
text-size: small;
|
||||||
padding: 0 0 0 20px;
|
padding: 0 0 0 20px;
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
|
use Psr\Log\LogLevel;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||||
@ -44,7 +45,7 @@ class ErrorHandler
|
|||||||
E_ERROR => 'Error',
|
E_ERROR => 'Error',
|
||||||
E_CORE_ERROR => 'Core Error',
|
E_CORE_ERROR => 'Core Error',
|
||||||
E_COMPILE_ERROR => 'Compile Error',
|
E_COMPILE_ERROR => 'Compile Error',
|
||||||
E_PARSE => 'Parse',
|
E_PARSE => 'Parse Error',
|
||||||
);
|
);
|
||||||
|
|
||||||
private $level;
|
private $level;
|
||||||
@ -108,7 +109,7 @@ class ErrorHandler
|
|||||||
* Sets a logger for the given channel.
|
* Sets a logger for the given channel.
|
||||||
*
|
*
|
||||||
* @param LoggerInterface $logger A logger interface
|
* @param LoggerInterface $logger A logger interface
|
||||||
* @param string $channel The channel associated with the logger (deprecation or emergency)
|
* @param string $channel The channel associated with the logger (deprecation, emergency or scream)
|
||||||
*/
|
*/
|
||||||
public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
|
public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
|
||||||
{
|
{
|
||||||
@ -120,10 +121,6 @@ class ErrorHandler
|
|||||||
*/
|
*/
|
||||||
public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
|
public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
|
||||||
{
|
{
|
||||||
if (0 === $this->level) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) {
|
if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) {
|
||||||
if (isset(self::$loggers['deprecation'])) {
|
if (isset(self::$loggers['deprecation'])) {
|
||||||
if (self::$stackedErrorLevels) {
|
if (self::$stackedErrorLevels) {
|
||||||
@ -182,6 +179,35 @@ class ErrorHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset(self::$loggers['scream']) && !(error_reporting() & $level)) {
|
||||||
|
if (self::$stackedErrorLevels) {
|
||||||
|
self::$stackedErrors[] = func_get_args();
|
||||||
|
} else {
|
||||||
|
switch ($level) {
|
||||||
|
case E_USER_ERROR:
|
||||||
|
case E_RECOVERABLE_ERROR:
|
||||||
|
$logLevel = LogLevel::ERROR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case E_WARNING:
|
||||||
|
case E_USER_WARNING:
|
||||||
|
$logLevel = LogLevel::WARNING;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$logLevel = LogLevel::NOTICE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$loggers['scream']->log($logLevel, $message, array(
|
||||||
|
'type' => $level,
|
||||||
|
'file' => $file,
|
||||||
|
'line' => $line,
|
||||||
|
'scream' => error_reporting(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +174,28 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
$handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, 'foo');
|
$handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, 'foo');
|
||||||
|
|
||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
|
|
||||||
|
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||||
|
|
||||||
|
$that = $this;
|
||||||
|
$logArgCheck = function ($level, $message, $context) use ($that) {
|
||||||
|
$that->assertEquals('Undefined variable: undefVar', $message);
|
||||||
|
$that->assertArrayHasKey('type', $context);
|
||||||
|
$that->assertEquals($context['type'], E_NOTICE);
|
||||||
|
};
|
||||||
|
|
||||||
|
$logger
|
||||||
|
->expects($this->once())
|
||||||
|
->method('log')
|
||||||
|
->will($this->returnCallback($logArgCheck))
|
||||||
|
;
|
||||||
|
|
||||||
|
$handler = ErrorHandler::register(E_NOTICE);
|
||||||
|
$handler->setLogger($logger, 'scream');
|
||||||
|
unset($undefVar);
|
||||||
|
@$undefVar++;
|
||||||
|
|
||||||
|
restore_error_handler();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
|
|
||||||
|
@ -46,11 +46,8 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
|
|||||||
public function lateCollect()
|
public function lateCollect()
|
||||||
{
|
{
|
||||||
if (null !== $this->logger) {
|
if (null !== $this->logger) {
|
||||||
$this->data = array(
|
$this->data = $this->computeErrorsCount();
|
||||||
'error_count' => $this->logger->countErrors(),
|
$this->data['logs'] = $this->sanitizeLogs($this->logger->getLogs());
|
||||||
'logs' => $this->sanitizeLogs($this->logger->getLogs()),
|
|
||||||
'deprecation_count' => $this->computeDeprecationCount()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +78,11 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
|
|||||||
return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0;
|
return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function countScreams()
|
||||||
|
{
|
||||||
|
return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@ -119,12 +121,21 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
|
|||||||
return $context;
|
return $context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function computeDeprecationCount()
|
private function computeErrorsCount()
|
||||||
{
|
{
|
||||||
$count = 0;
|
$count = array(
|
||||||
|
'error_count' => $this->logger->countErrors(),
|
||||||
|
'deprecation_count' => 0,
|
||||||
|
'scream_count' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
foreach ($this->logger->getLogs() as $log) {
|
foreach ($this->logger->getLogs() as $log) {
|
||||||
if (isset($log['context']['type']) && ErrorHandler::TYPE_DEPRECATION === $log['context']['type']) {
|
if (isset($log['context']['type'])) {
|
||||||
$count++;
|
if (ErrorHandler::TYPE_DEPRECATION === $log['context']['type']) {
|
||||||
|
++$count['deprecation_count'];
|
||||||
|
} elseif (isset($log['context']['scream'])) {
|
||||||
|
++$count['scream_count'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class LoggerDataCollectorTest extends \PHPUnit_Framework_TestCase
|
|||||||
/**
|
/**
|
||||||
* @dataProvider getCollectTestData
|
* @dataProvider getCollectTestData
|
||||||
*/
|
*/
|
||||||
public function testCollect($nb, $logs, $expectedLogs, $expectedDeprecationCount)
|
public function testCollect($nb, $logs, $expectedLogs, $expectedDeprecationCount, $expectedScreamCount)
|
||||||
{
|
{
|
||||||
$logger = $this->getMock('Symfony\Component\HttpKernel\Log\DebugLoggerInterface');
|
$logger = $this->getMock('Symfony\Component\HttpKernel\Log\DebugLoggerInterface');
|
||||||
$logger->expects($this->once())->method('countErrors')->will($this->returnValue($nb));
|
$logger->expects($this->once())->method('countErrors')->will($this->returnValue($nb));
|
||||||
@ -32,6 +32,7 @@ class LoggerDataCollectorTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertSame($nb, $c->countErrors());
|
$this->assertSame($nb, $c->countErrors());
|
||||||
$this->assertSame($expectedLogs ? $expectedLogs : $logs, $c->getLogs());
|
$this->assertSame($expectedLogs ? $expectedLogs : $logs, $c->getLogs());
|
||||||
$this->assertSame($expectedDeprecationCount, $c->countDeprecations());
|
$this->assertSame($expectedDeprecationCount, $c->countDeprecations());
|
||||||
|
$this->assertSame($expectedScreamCount, $c->countScreams());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCollectTestData()
|
public function getCollectTestData()
|
||||||
@ -41,28 +42,33 @@ class LoggerDataCollectorTest extends \PHPUnit_Framework_TestCase
|
|||||||
1,
|
1,
|
||||||
array(array('message' => 'foo', 'context' => array())),
|
array(array('message' => 'foo', 'context' => array())),
|
||||||
null,
|
null,
|
||||||
0
|
0,
|
||||||
|
0,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
1,
|
1,
|
||||||
array(array('message' => 'foo', 'context' => array('foo' => fopen(__FILE__, 'r')))),
|
array(array('message' => 'foo', 'context' => array('foo' => fopen(__FILE__, 'r')))),
|
||||||
array(array('message' => 'foo', 'context' => array('foo' => 'Resource(stream)'))),
|
array(array('message' => 'foo', 'context' => array('foo' => 'Resource(stream)'))),
|
||||||
0
|
0,
|
||||||
|
0,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
1,
|
1,
|
||||||
array(array('message' => 'foo', 'context' => array('foo' => new \stdClass()))),
|
array(array('message' => 'foo', 'context' => array('foo' => new \stdClass()))),
|
||||||
array(array('message' => 'foo', 'context' => array('foo' => 'Object(stdClass)'))),
|
array(array('message' => 'foo', 'context' => array('foo' => 'Object(stdClass)'))),
|
||||||
0
|
0,
|
||||||
|
0,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
1,
|
1,
|
||||||
array(
|
array(
|
||||||
array('message' => 'foo', 'context' => array('type' => ErrorHandler::TYPE_DEPRECATION)),
|
array('message' => 'foo', 'context' => array('type' => ErrorHandler::TYPE_DEPRECATION)),
|
||||||
array('message' => 'foo2', 'context' => array('type' => ErrorHandler::TYPE_DEPRECATION))
|
array('message' => 'foo2', 'context' => array('type' => ErrorHandler::TYPE_DEPRECATION)),
|
||||||
|
array('message' => 'foo3', 'context' => array('type' => E_USER_WARNING, 'scream' => 0)),
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
|
1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user