[Debug] add a screaming mode to ErrorHandler

This commit is contained in:
Nicolas Grekas 2014-02-04 21:08:27 +01:00
parent 48c9985e73
commit 5cc817d1bd
7 changed files with 109 additions and 28 deletions

View File

@ -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>

View File

@ -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 %}

View File

@ -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;

View File

@ -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;
} }

View File

@ -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();

View File

@ -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'];
}
} }
} }

View File

@ -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,
), ),
); );
} }