Skip to content

Commit

Permalink
merged branch Koc/critical-errors-logging (PR #5863)
Browse files Browse the repository at this point in the history
This PR was merged into the master branch.

Commits
-------

acfc750 #2042 initial implementation of fatal error handler

Discussion
----------

Display traces for fatal errors

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: looks like yes
Fixes the following tickets: #2042 (partly)
License of the code: MIT

Output looks like on screen http://easycaptures.com/fs/uploaded/737/1191436899.png . I've added one line to css to prevent displaying standard xdebug trace http://easycaptures.com/fs/uploaded/737/5939488074.png

---------------------------------------------------------------------------

by Koc at 2012-11-08T21:55:41Z

So, community please advice me, how can I trigger `KernelEvents::EXCEPTION` event in `ErrorHandler` or `ExceptionHandler`? Or should I provide other event for this?

---------------------------------------------------------------------------

by stof at 2012-11-08T22:03:23Z

@Koc Don't. the exception handler is there to be the safe guard when developing, and does not depend on the kernel (which would be required to trigger the event). If you were triggering the listener again, it would mean that any exception thrown in a listener would lead to a loop.
And if it is for the fatal error handling, you simply cannot be sure the kernel is still available (and even less in a wokring state) at this point.

---------------------------------------------------------------------------

by Koc at 2012-11-08T22:06:31Z

But how can I notify logger (which will send me mail or just log this situation)?

---------------------------------------------------------------------------

by fabpot at 2012-11-09T07:33:41Z

The error handler is only registered when in debug mode in the Kernel and can be triggered very early in the handling of a request (even before we have access to the dispatcher or anything else). So, the current PR looks fine to me (apart from the typo and the lack of unit tests).

---------------------------------------------------------------------------

by Koc at 2012-11-09T09:13:03Z

> The error handler is only registered when in debug mode

Ooh! I haven't see that before. But the goal - be notified about errors by email or log-file. Like now exceptions with traces from site emails to me.

---------------------------------------------------------------------------

by fabpot at 2012-11-09T09:20:54Z

I think there are two goals. The first one being to have nice pages in the development environment when a fatal error occurs. And this PR addresses that feature quite nicely. The second can be addressed in another PR.

---------------------------------------------------------------------------

by henrikbjorn at 2012-11-14T11:50:22Z

I have some questions about the ErrorHandler. Is there a reason for it only to be registered in an debug environment (which prod is not). Would assume that if i enable the ErrorHandler in productions aswell Monolog would log thoose instead of them just vanishing?

---------------------------------------------------------------------------

by Koc at 2012-11-14T12:01:50Z

I am thinking about it too. But as Fabien says it will another PR

---------------------------------------------------------------------------

by GromNaN at 2012-11-18T10:38:09Z

You should add a memory reserve to be able to handle "Out of memory" errors.
An example is here :
https://github.com/LExpress/raven-php/blob/513d628966ffc356d827ecc48ec557675bd116b8/lib/Raven/ErrorHandler.php#L91
https://github.com/LExpress/raven-php/blob/513d628966ffc356d827ecc48ec557675bd116b8/lib/Raven/ErrorHandler.php#L62

---------------------------------------------------------------------------

by fabpot at 2012-11-28T11:35:21Z

@Koc can you finish this PR (probably by integrating the memory reserve as explained by @GromNaN)?

---------------------------------------------------------------------------

by Koc at 2012-11-28T11:46:12Z

of course, on this weekend

---------------------------------------------------------------------------

by Koc at 2012-12-02T17:44:44Z

@fabpot done
  • Loading branch information
fabpot committed Dec 5, 2012
2 parents 425f6f5 + acfc750 commit 559fa8c
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 7 deletions.
34 changes: 34 additions & 0 deletions src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\HttpKernel\Debug;

use Symfony\Component\HttpKernel\Exception\FatalErrorException;

/**
* ErrorHandler.
*
Expand All @@ -28,10 +30,16 @@ class ErrorHandler
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
E_DEPRECATED => 'Deprecated',
E_USER_DEPRECATED => 'User Deprecated',
E_ERROR => 'Error',
E_CORE_ERROR => 'Core Error',
E_COMPILE_ERROR => 'Compile Error',
E_PARSE => 'Parse',
);

private $level;

private $reservedMemory;

/**
* Register the error handler.
*
Expand All @@ -45,6 +53,8 @@ public static function register($level = null)
$handler->setLevel($level);

set_error_handler(array($handler, 'handle'));
register_shutdown_function(array($handler, 'handleFatal'));
$handler->reservedMemory = str_repeat('x', 10240);

return $handler;
}
Expand All @@ -69,4 +79,28 @@ public function handle($level, $message, $file, $line, $context)

return false;
}

public function handleFatal()
{
if (null === $error = error_get_last()) {
return;
}

unset($this->reservedMemory);
$type = $error['type'];
if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
return;
}

// get current exception handler
$exceptionHandler = set_exception_handler(function() {});
restore_exception_handler();

if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
$level = isset($this->levels[$type]) ? $this->levels[$type] : $type;
$message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']);
$exception = new FatalErrorException($message, 0, $type, $error['file'], $error['line']);
$exceptionHandler[0]->handle($exception);
}
}
}
3 changes: 3 additions & 0 deletions src/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php
Expand Up @@ -234,6 +234,9 @@ private function decorate($content, $css)
img { border: 0; }
#sf-resetcontent { width:970px; margin:0 auto; }
$css
.xdebug-error {
display: none;
}
</style>
</head>
<body>
Expand Down
22 changes: 22 additions & 0 deletions src/Symfony/Component/HttpKernel/Exception/FatalErrorException.php
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\Exception;

/**
* Fatal Error Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*/
class FatalErrorException extends \ErrorException
{

}
36 changes: 35 additions & 1 deletion src/Symfony/Component/HttpKernel/Exception/FlattenException.php
Expand Up @@ -47,7 +47,7 @@ public static function create(\Exception $exception, $statusCode = null, array $

$e->setStatusCode($statusCode);
$e->setHeaders($headers);
$e->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
$e->setTraceFromException($exception);
$e->setClass(get_class($exception));
$e->setFile($exception->getFile());
$e->setLine($exception->getLine());
Expand Down Expand Up @@ -168,6 +168,40 @@ public function getTrace()
return $this->trace;
}

public function setTraceFromException(\Exception $exception)
{
$trace = $exception->getTrace();

if ($exception instanceof FatalErrorException) {
if (function_exists('xdebug_get_function_stack')) {
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 4);

foreach ($trace as $i => $frame) {
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
if (!isset($frame['type'])) {
$trace[$i]['type'] = '??';
}

if ('dynamic' === $trace[$i]['type']) {
$trace[$i]['type'] = '->';
} elseif ('static' === $trace[$i]['type']) {
$trace[$i]['type'] = '::';
}

// XDebug also has a different name for the parameters array
if (isset($frame['params']) && !isset($frame['args'])) {
$trace[$i]['args'] = $frame['params'];
unset($trace[$i]['params']);
}
}
} else {
$trace = array_slice(array_reverse($trace), 1);
}
}

$this->setTrace($trace, $exception->getFile(), $exception->getLine());
}

public function setTrace($trace, $file, $line)
{
$this->trace = array();
Expand Down
Expand Up @@ -12,7 +12,6 @@
namespace Symfony\Component\HttpKernel\Tests\Debug;

use Symfony\Component\HttpKernel\Debug\ErrorHandler;
use Symfony\Component\HttpKernel\Debug\ErrorException;

/**
* ErrorHandlerTest
Expand Down Expand Up @@ -48,10 +47,10 @@ public function testHandle()

$handler = ErrorHandler::register(3);
try {
$handler->handle(1, 'foo', 'foo.php', 12, 'foo');
$handler->handle(111, 'foo', 'foo.php', 12, 'foo');
} catch (\ErrorException $e) {
$this->assertSame('1: foo in foo.php line 12', $e->getMessage());
$this->assertSame(1, $e->getSeverity());
$this->assertSame('111: foo in foo.php line 12', $e->getMessage());
$this->assertSame(111, $e->getSeverity());
$this->assertSame('foo.php', $e->getFile());
$this->assertSame(12, $e->getLine());
}
Expand Down
Expand Up @@ -156,14 +156,14 @@ public function testFile(\Exception $exception)
public function testToArray(\Exception $exception, $statusCode)
{
$flattened = FlattenException::create($exception);
$flattened->setTrace(array(),'foo.php',123);
$flattened->setTrace(array(), 'foo.php', 123);

$this->assertEquals(array(
array(
'message'=> 'test',
'class'=>'Exception',
'trace'=>array(array(
'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '', 'file' => 'foo.php','line' => 123,
'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '', 'file' => 'foo.php', 'line' => 123,
'args' => array()
)),
)
Expand Down

0 comments on commit 559fa8c

Please sign in to comment.