feature #12081 [FrameworkBundle] enable ErrorHandler in prod (nicolas-grekas)
This PR was merged into the 2.6-dev branch. Discussion ---------- [FrameworkBundle] enable ErrorHandler in prod | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #11053, #8281 | License | MIT | Doc PR | - - a new debug.error_handler service is the registered PHP error handler, with ErrorHandler::register() as factory - ErrorHandler::register() is patched so that it checks if the currently registered error handler is an instance of ErrorHandler - in which case it returns this instance and don't create a new one. - DebugHandlersListener now listen to ConsoleEvents and re-injects fatal errors within the $app->renderException code path - DebugHandlersListener also has a new $scream parameter to control is silenced errors are logged or not Commits -------fac3cc4
[FrameworkBundle] register ErrorHandler at boot time4acf5d3
[Debug] make screaming configurable4d0ab7d
[FrameworkBundle] enable ErrorHandler in prod
This commit is contained in:
commit
3da6fc22c6
@ -127,14 +127,12 @@ class FrameworkExtension extends Extension
|
|||||||
$definition = $container->findDefinition('debug.debug_handlers_listener');
|
$definition = $container->findDefinition('debug.debug_handlers_listener');
|
||||||
|
|
||||||
if ($container->hasParameter('templating.helper.code.file_link_format')) {
|
if ($container->hasParameter('templating.helper.code.file_link_format')) {
|
||||||
$definition->replaceArgument(4, '%templating.helper.code.file_link_format%');
|
$definition->replaceArgument(5, '%templating.helper.code.file_link_format%');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($container->getParameter('kernel.debug')) {
|
if ($container->getParameter('kernel.debug')) {
|
||||||
$loader->load('debug.xml');
|
$loader->load('debug.xml');
|
||||||
|
|
||||||
$definition->replaceArgument(0, array(new Reference('http_kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE), 'terminateWithException'));
|
|
||||||
|
|
||||||
$definition = $container->findDefinition('http_kernel');
|
$definition = $container->findDefinition('http_kernel');
|
||||||
$definition->replaceArgument(2, new Reference('debug.controller_resolver'));
|
$definition->replaceArgument(2, new Reference('debug.controller_resolver'));
|
||||||
|
|
||||||
@ -150,6 +148,8 @@ class FrameworkExtension extends Extension
|
|||||||
$this->addClassesToCompile(array(
|
$this->addClassesToCompile(array(
|
||||||
'Symfony\\Component\\Config\\FileLocator',
|
'Symfony\\Component\\Config\\FileLocator',
|
||||||
|
|
||||||
|
'Symfony\\Component\\Debug\\ErrorHandler',
|
||||||
|
|
||||||
'Symfony\\Component\\EventDispatcher\\Event',
|
'Symfony\\Component\\EventDispatcher\\Event',
|
||||||
'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher',
|
'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher',
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtra
|
|||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FragmentRendererPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FragmentRendererPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass;
|
||||||
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||||
use Symfony\Component\DependencyInjection\Scope;
|
use Symfony\Component\DependencyInjection\Scope;
|
||||||
@ -46,6 +47,8 @@ class FrameworkBundle extends Bundle
|
|||||||
{
|
{
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
|
ErrorHandler::register($this->container->getParameter('debug.error_handler.throw_at'));
|
||||||
|
|
||||||
if ($trustedProxies = $this->container->getParameter('kernel.trusted_proxies')) {
|
if ($trustedProxies = $this->container->getParameter('kernel.trusted_proxies')) {
|
||||||
Request::setTrustedProxies($trustedProxies);
|
Request::setTrustedProxies($trustedProxies);
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<parameter key="debug.event_dispatcher.class">Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher</parameter>
|
<parameter key="debug.event_dispatcher.class">Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher</parameter>
|
||||||
<parameter key="debug.container.dump">%kernel.cache_dir%/%kernel.container_class%.xml</parameter>
|
<parameter key="debug.container.dump">%kernel.cache_dir%/%kernel.container_class%.xml</parameter>
|
||||||
<parameter key="debug.controller_resolver.class">Symfony\Component\HttpKernel\Controller\TraceableControllerResolver</parameter>
|
<parameter key="debug.controller_resolver.class">Symfony\Component\HttpKernel\Controller\TraceableControllerResolver</parameter>
|
||||||
|
<parameter key="debug.error_handler.throw_at">-1</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
|
|
||||||
<services>
|
<services>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
<parameters>
|
<parameters>
|
||||||
<parameter key="debug.debug_handlers_listener.class">Symfony\Component\HttpKernel\EventListener\DebugHandlersListener</parameter>
|
<parameter key="debug.debug_handlers_listener.class">Symfony\Component\HttpKernel\EventListener\DebugHandlersListener</parameter>
|
||||||
<parameter key="debug.stopwatch.class">Symfony\Component\Stopwatch\Stopwatch</parameter>
|
<parameter key="debug.stopwatch.class">Symfony\Component\Stopwatch\Stopwatch</parameter>
|
||||||
|
<parameter key="debug.error_handler.throw_at">0</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
|
|
||||||
<services>
|
<services>
|
||||||
@ -16,7 +17,8 @@
|
|||||||
<argument /><!-- Exception handler -->
|
<argument /><!-- Exception handler -->
|
||||||
<argument type="service" id="logger" on-invalid="null" />
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
<argument /><!-- Log levels map for enabled error levels -->
|
<argument /><!-- Log levels map for enabled error levels -->
|
||||||
<argument>%kernel.debug%</argument>
|
<argument>null</argument>
|
||||||
|
<argument>true</argument>
|
||||||
<argument>null</argument><!-- %templating.helper.code.file_link_format% -->
|
<argument>null</argument><!-- %templating.helper.code.file_link_format% -->
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
@ -124,9 +124,15 @@ class ErrorHandler
|
|||||||
|
|
||||||
$handler = new static();
|
$handler = new static();
|
||||||
$levels &= $handler->thrownErrors;
|
$levels &= $handler->thrownErrors;
|
||||||
set_error_handler(array($handler, 'handleError'), $levels);
|
$prev = set_error_handler(array($handler, 'handleError'), $levels);
|
||||||
$handler->throwAt($throw ? $levels : 0, true);
|
$prev = is_array($prev) ? $prev[0] : null;
|
||||||
|
if ($prev instanceof self) {
|
||||||
|
restore_error_handler();
|
||||||
|
$handler = $prev;
|
||||||
|
} else {
|
||||||
$handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
|
$handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
|
||||||
|
}
|
||||||
|
$handler->throwAt($throw ? $levels : 0, true);
|
||||||
|
|
||||||
return $handler;
|
return $handler;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,30 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
error_reporting($this->errorReporting);
|
error_reporting($this->errorReporting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRegister()
|
||||||
|
{
|
||||||
|
$handler = ErrorHandler::register();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->assertSame($handler, ErrorHandler::register());
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
restore_error_handler();
|
||||||
|
restore_exception_handler();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_error_handler();
|
||||||
|
restore_exception_handler();
|
||||||
|
|
||||||
|
if (isset($e)) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function testNotice()
|
public function testNotice()
|
||||||
{
|
{
|
||||||
ErrorHandler::register();
|
ErrorHandler::register();
|
||||||
|
@ -14,8 +14,14 @@ namespace Symfony\Component\HttpKernel\EventListener;
|
|||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Debug\ErrorHandler;
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
use Symfony\Component\Debug\ExceptionHandler;
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
|
use Symfony\Component\EventDispatcher\Event;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
||||||
use Symfony\Component\HttpKernel\KernelEvents;
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\Console\ConsoleEvents;
|
||||||
|
use Symfony\Component\Console\Event\ConsoleEvent;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures errors and exceptions handlers.
|
* Configures errors and exceptions handlers.
|
||||||
@ -27,46 +33,78 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
private $exceptionHandler;
|
private $exceptionHandler;
|
||||||
private $logger;
|
private $logger;
|
||||||
private $levels;
|
private $levels;
|
||||||
private $debug;
|
private $throwAt;
|
||||||
|
private $scream;
|
||||||
|
private $fileLinkFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param callable $exceptionHandler A handler that will be called on Exception
|
* @param callable|null $exceptionHandler A handler that will be called on Exception
|
||||||
* @param LoggerInterface|null $logger A PSR-3 logger
|
* @param LoggerInterface|null $logger A PSR-3 logger
|
||||||
* @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
|
* @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
|
||||||
* @param bool $debug Enables/disables debug mode
|
* @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value
|
||||||
|
* @param bool $scream Enables/disables screaming mode, where even silenced errors are logged
|
||||||
* @param string $fileLinkFormat The format for links to source files
|
* @param string $fileLinkFormat The format for links to source files
|
||||||
*/
|
*/
|
||||||
public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $debug = true, $fileLinkFormat = null)
|
public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $throwAt = -1, $scream = true, $fileLinkFormat = null)
|
||||||
{
|
{
|
||||||
$this->exceptionHandler = $exceptionHandler;
|
$this->exceptionHandler = $exceptionHandler;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->levels = $levels;
|
$this->levels = $levels;
|
||||||
$this->debug = $debug;
|
$this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? -1 : null));
|
||||||
|
$this->scream = (bool) $scream;
|
||||||
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
|
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure()
|
/**
|
||||||
|
* Configures the error handler.
|
||||||
|
*
|
||||||
|
* @param Event|null $event The triggering event
|
||||||
|
* @param string|null $eventName The triggering event name
|
||||||
|
* @param EventDispatcherInterface|null $eventDispatcher The dispatcher used to trigger $event
|
||||||
|
*/
|
||||||
|
public function configure(Event $event = null, $eventName = null, EventDispatcherInterface $eventDispatcher = null)
|
||||||
{
|
{
|
||||||
if ($this->logger) {
|
if (null !== $eventDispatcher) {
|
||||||
|
foreach (array_keys(static::getSubscribedEvents()) as $name) {
|
||||||
|
$eventDispatcher->removeListener($name, array($this, 'configure'));
|
||||||
|
}
|
||||||
|
}
|
||||||
$handler = set_error_handler('var_dump', 0);
|
$handler = set_error_handler('var_dump', 0);
|
||||||
$handler = is_array($handler) ? $handler[0] : null;
|
$handler = is_array($handler) ? $handler[0] : null;
|
||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
if ($handler instanceof ErrorHandler) {
|
if ($handler instanceof ErrorHandler) {
|
||||||
if ($this->debug) {
|
if ($this->logger) {
|
||||||
$handler->throwAt(-1);
|
|
||||||
}
|
|
||||||
$handler->setDefaultLogger($this->logger, $this->levels);
|
$handler->setDefaultLogger($this->logger, $this->levels);
|
||||||
if (is_array($this->levels)) {
|
if (is_array($this->levels)) {
|
||||||
$scream = 0;
|
$scream = 0;
|
||||||
foreach ($this->levels as $type => $log) {
|
foreach ($this->levels as $type => $log) {
|
||||||
$scream |= $type;
|
$scream |= $type;
|
||||||
}
|
}
|
||||||
$this->levels = $scream;
|
} else {
|
||||||
|
$scream = null === $this->levels ? E_ALL | E_STRICT : $this->levels;
|
||||||
}
|
}
|
||||||
$handler->screamAt($this->levels);
|
if ($this->scream) {
|
||||||
|
$handler->screamAt($scream);
|
||||||
}
|
}
|
||||||
$this->logger = $this->levels = null;
|
$this->logger = $this->levels = null;
|
||||||
}
|
}
|
||||||
|
if (null !== $this->throwAt) {
|
||||||
|
$handler->throwAt($this->throwAt, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$this->exceptionHandler) {
|
||||||
|
if ($event instanceof KernelEvent) {
|
||||||
|
$this->exceptionHandler = array($event->getKernel(), 'terminateWithException');
|
||||||
|
} elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) {
|
||||||
|
$output = $event->getOutput();
|
||||||
|
if ($output instanceof ConsoleOutputInterface) {
|
||||||
|
$output = $output->getErrorOutput();
|
||||||
|
}
|
||||||
|
$this->exceptionHandler = function ($e) use ($app, $output) {
|
||||||
|
$app->renderException($e, $output);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($this->exceptionHandler) {
|
if ($this->exceptionHandler) {
|
||||||
$handler = set_exception_handler('var_dump');
|
$handler = set_exception_handler('var_dump');
|
||||||
$handler = is_array($handler) ? $handler[0] : null;
|
$handler = is_array($handler) ? $handler[0] : null;
|
||||||
@ -78,14 +116,22 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
if ($handler instanceof ExceptionHandler) {
|
if ($handler instanceof ExceptionHandler) {
|
||||||
$handler->setHandler($this->exceptionHandler);
|
$handler->setHandler($this->exceptionHandler);
|
||||||
|
if (null !== $this->fileLinkFormat) {
|
||||||
$handler->setFileLinkFormat($this->fileLinkFormat);
|
$handler->setFileLinkFormat($this->fileLinkFormat);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
$this->exceptionHandler = null;
|
$this->exceptionHandler = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getSubscribedEvents()
|
public static function getSubscribedEvents()
|
||||||
{
|
{
|
||||||
return array(KernelEvents::REQUEST => array('configure', 2048));
|
$events = array(KernelEvents::REQUEST => array('configure', 2048));
|
||||||
|
|
||||||
|
if (defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
|
||||||
|
$events[ConsoleEvents::COMMAND] = array('configure', 2048);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $events;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,17 @@
|
|||||||
namespace Symfony\Component\HttpKernel\Tests\EventListener;
|
namespace Symfony\Component\HttpKernel\Tests\EventListener;
|
||||||
|
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
|
use Symfony\Component\Console\Event\ConsoleEvent;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\ConsoleEvents;
|
||||||
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
use Symfony\Component\Debug\ErrorHandler;
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
use Symfony\Component\Debug\ExceptionHandler;
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener;
|
use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DebugHandlersListenerTest
|
* DebugHandlersListenerTest
|
||||||
@ -53,4 +61,48 @@ class DebugHandlersListenerTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertArrayHasKey(E_DEPRECATED, $loggers);
|
$this->assertArrayHasKey(E_DEPRECATED, $loggers);
|
||||||
$this->assertSame(array($logger, LogLevel::INFO), $loggers[E_DEPRECATED]);
|
$this->assertSame(array($logger, LogLevel::INFO), $loggers[E_DEPRECATED]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testConsoleEvent()
|
||||||
|
{
|
||||||
|
$dispatcher = new EventDispatcher();
|
||||||
|
$listener = new DebugHandlersListener(null);
|
||||||
|
$app = $this->getMock('Symfony\Component\Console\Application');
|
||||||
|
$app->expects($this->once())->method('getHelperSet')->will($this->returnValue(new HelperSet()));
|
||||||
|
$command = new Command(__FUNCTION__);
|
||||||
|
$command->setApplication($app);
|
||||||
|
$event = new ConsoleEvent($command, new ArgvInput(), new ConsoleOutput());
|
||||||
|
|
||||||
|
$dispatcher->addSubscriber($listener);
|
||||||
|
|
||||||
|
$xListeners = array(
|
||||||
|
KernelEvents::REQUEST => array(array($listener, 'configure')),
|
||||||
|
ConsoleEvents::COMMAND => array(array($listener, 'configure')),
|
||||||
|
);
|
||||||
|
$this->assertSame($xListeners, $dispatcher->getListeners());
|
||||||
|
|
||||||
|
$exception = null;
|
||||||
|
$eHandler = new ErrorHandler();
|
||||||
|
set_error_handler(array($eHandler, 'handleError'));
|
||||||
|
set_exception_handler(array($eHandler, 'handleException'));
|
||||||
|
try {
|
||||||
|
$dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
|
||||||
|
} catch (\Exception $exception) {
|
||||||
|
}
|
||||||
|
restore_exception_handler();
|
||||||
|
restore_error_handler();
|
||||||
|
|
||||||
|
if (null !== $exception) {
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame(array(), $dispatcher->getListeners());
|
||||||
|
|
||||||
|
$xHandler = $eHandler->setExceptionHandler('var_dump');
|
||||||
|
$this->assertInstanceOf('Closure', $xHandler);
|
||||||
|
|
||||||
|
$app->expects($this->once())
|
||||||
|
->method('renderException');
|
||||||
|
|
||||||
|
$xHandler(new \Exception());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user