Skip to content
This repository was archived by the owner on Dec 9, 2023. It is now read-only.

Commit b5a2167

Browse files
committed
[Debug] Better error handling
1. Send the raw exception in the log context instead of custom formatting 2. Add config option to log in Symfony all PHP errors
1 parent 6019275 commit b5a2167

File tree

3 files changed

+136
-91
lines changed

3 files changed

+136
-91
lines changed

ErrorHandler.php

Lines changed: 44 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Debug\Exception\FatalErrorException;
1818
use Symfony\Component\Debug\Exception\FatalThrowableError;
1919
use Symfony\Component\Debug\Exception\OutOfMemoryException;
20+
use Symfony\Component\Debug\Exception\SilencedErrorContext;
2021
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
2122
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
2223
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
@@ -29,7 +30,7 @@
2930
* - thrownErrors: errors thrown as \ErrorException
3031
* - loggedErrors: logged errors, when not @-silenced
3132
* - scopedErrors: errors thrown or logged with their local context
32-
* - tracedErrors: errors logged with their stack trace, only once for repeated errors
33+
* - tracedErrors: errors logged with their stack trace
3334
* - screamedErrors: never @-silenced errors
3435
*
3536
* Each error level can be logged by a dedicated PSR-3 logger object.
@@ -43,6 +44,7 @@
4344
* can see them and weight them as more important to fix than others of the same level.
4445
*
4546
* @author Nicolas Grekas <[email protected]>
47+
* @author Grégoire Pineau <[email protected]>
4648
*/
4749
class ErrorHandler
4850
{
@@ -88,7 +90,6 @@ class ErrorHandler
8890
private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
8991
private $loggedErrors = 0;
9092

91-
private $loggedTraces = array();
9293
private $isRecursive = 0;
9394
private $isRoot = false;
9495
private $exceptionHandler;
@@ -221,7 +222,7 @@ public function setLoggers(array $loggers)
221222

222223
if ($flush) {
223224
foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
224-
$type = $log[2]['type'];
225+
$type = $log[2]['exception']->getSeverity();
225226
if (!isset($flush[$type])) {
226227
$this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
227228
} elseif ($this->loggers[$type][0]) {
@@ -361,6 +362,8 @@ private function reRegister($prev)
361362
*/
362363
public function handleError($type, $message, $file, $line, array $context, array $backtrace = null)
363364
{
365+
// Level is the current error reporting level to manage silent error.
366+
// Strong errors are not authorized to be silenced.
364367
$level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
365368
$log = $this->loggedErrors & $type;
366369
$throw = $this->thrownErrors & $type & $level;
@@ -379,18 +382,22 @@ public function handleError($type, $message, $file, $line, array $context, array
379382
return true;
380383
}
381384

382-
if ($throw) {
383-
if (null !== self::$toStringException) {
384-
$throw = self::$toStringException;
385-
self::$toStringException = null;
386-
} elseif (($this->scopedErrors & $type) && class_exists(ContextErrorException::class)) {
387-
$throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
388-
} else {
389-
$throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
390-
}
385+
$logMessage = $this->levels[$type].': '.$message;
391386

387+
if (null !== self::$toStringException) {
388+
$errorAsException = self::$toStringException;
389+
self::$toStringException = null;
390+
} elseif (!$throw && !($type & $level)) {
391+
$errorAsException = new SilencedErrorContext($type, $file, $line);
392+
} elseif ($this->scopedErrors & $type) {
393+
$errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context);
394+
} else {
395+
$errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
396+
}
397+
398+
if ($throw) {
392399
if (E_USER_ERROR & $type) {
393-
$backtrace = $backtrace ?: $throw->getTrace();
400+
$backtrace = $backtrace ?: $errorAsException->getTrace();
394401

395402
for ($i = 1; isset($backtrace[$i]); ++$i) {
396403
if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
@@ -410,7 +417,7 @@ public function handleError($type, $message, $file, $line, array $context, array
410417
if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
411418
if (1 === $i) {
412419
// On HHVM
413-
$throw = $e;
420+
$errorAsException = $e;
414421
break;
415422
}
416423
self::$toStringException = $e;
@@ -421,7 +428,7 @@ public function handleError($type, $message, $file, $line, array $context, array
421428

422429
if (1 < $i) {
423430
// On PHP (not on HHVM), display the original error message instead of the default one.
424-
$this->handleException($throw);
431+
$this->handleException($errorAsException);
425432

426433
// Stop the process by giving back the error to the native handler.
427434
return false;
@@ -430,47 +437,31 @@ public function handleError($type, $message, $file, $line, array $context, array
430437
}
431438
}
432439

433-
throw $throw;
434-
}
435-
436-
// For duplicated errors, log the trace only once
437-
$e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
438-
$trace = true;
439-
440-
if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
441-
$trace = false;
442-
} else {
443-
$this->loggedTraces[$e] = 1;
440+
throw $errorAsException;
444441
}
445442

446-
$e = compact('type', 'file', 'line', 'level');
447-
448-
if ($type & $level) {
449-
if ($this->scopedErrors & $type) {
450-
$e['scope_vars'] = $context;
451-
if ($trace) {
452-
$e['stack'] = $backtrace ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
453-
}
454-
} elseif ($trace) {
455-
if (null === $backtrace) {
456-
$e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
457-
} else {
458-
foreach ($backtrace as &$frame) {
459-
unset($frame['args'], $frame);
460-
}
461-
$e['stack'] = $backtrace;
462-
}
443+
if (!($this->tracedErrors & $type) && $errorAsException instanceof \Exception) {
444+
static $freeTrace = null;
445+
if (null === $freeTrace) {
446+
$freeTrace = \Closure::bind(function ($e) { $e->trace = array(); }, null, \Exception::class);
463447
}
448+
$freeTrace($errorAsException);
464449
}
465450

466451
if ($this->isRecursive) {
467452
$log = 0;
468453
} elseif (self::$stackedErrorLevels) {
469-
self::$stackedErrors[] = array($this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
454+
self::$stackedErrors[] = array(
455+
$this->loggers[$type][0],
456+
($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG,
457+
$logMessage,
458+
array('exception' => $errorAsException),
459+
);
470460
} else {
471461
try {
472462
$this->isRecursive = true;
473-
$this->loggers[$type][0]->log(($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
463+
$level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG;
464+
$this->loggers[$type][0]->log($level, $logMessage, array('exception' => $errorAsException));
474465
} finally {
475466
$this->isRecursive = false;
476467
}
@@ -495,20 +486,13 @@ public function handleException($exception, array $error = null)
495486
$type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
496487

497488
if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
498-
$e = array(
499-
'type' => $type,
500-
'file' => $exception->getFile(),
501-
'line' => $exception->getLine(),
502-
'level' => error_reporting(),
503-
'stack' => $exception->getTrace(),
504-
);
505489
if ($exception instanceof FatalErrorException) {
506490
if ($exception instanceof FatalThrowableError) {
507491
$error = array(
508492
'type' => $type,
509493
'message' => $message = $exception->getMessage(),
510-
'file' => $e['file'],
511-
'line' => $e['line'],
494+
'file' => $exception->getFile(),
495+
'line' => $exception->getLine(),
512496
);
513497
} else {
514498
$message = 'Fatal '.$exception->getMessage();
@@ -523,7 +507,7 @@ public function handleException($exception, array $error = null)
523507
}
524508
}
525509
if ($this->loggedErrors & $type) {
526-
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
510+
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, array('exception' => $exception));
527511
}
528512
if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
529513
foreach ($this->getFatalErrorHandlers() as $handler) {
@@ -629,19 +613,19 @@ public static function unstackErrors()
629613
$level = array_pop(self::$stackedErrorLevels);
630614

631615
if (null !== $level) {
632-
$e = error_reporting($level);
633-
if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
616+
$errorReportingLevel = error_reporting($level);
617+
if ($errorReportingLevel !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
634618
// If the user changed the error level, do not overwrite it
635-
error_reporting($e);
619+
error_reporting($errorReportingLevel);
636620
}
637621
}
638622

639623
if (empty(self::$stackedErrorLevels)) {
640624
$errors = self::$stackedErrors;
641625
self::$stackedErrors = array();
642626

643-
foreach ($errors as $e) {
644-
$e[0]->log($e[1], $e[2], $e[3]);
627+
foreach ($errors as $error) {
628+
$error[0]->log($error[1], $error[2], $error[3]);
645629
}
646630
}
647631
}

Exception/SilencedErrorContext.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Debug\Exception;
13+
14+
/**
15+
* Data Object that represents a Silenced Error.
16+
*
17+
* @author Grégoire Pineau <[email protected]>
18+
*/
19+
class SilencedErrorContext implements \JsonSerializable
20+
{
21+
private $severity;
22+
private $file;
23+
private $line;
24+
25+
public function __construct($severity, $file, $line)
26+
{
27+
$this->severity = $severity;
28+
$this->file = $file;
29+
$this->line = $line;
30+
}
31+
32+
public function getSeverity()
33+
{
34+
return $this->severity;
35+
}
36+
37+
public function getFile()
38+
{
39+
return $this->file;
40+
}
41+
42+
public function getLine()
43+
{
44+
return $this->line;
45+
}
46+
47+
public function JsonSerialize()
48+
{
49+
return array(
50+
'severity' => $this->severity,
51+
'file' => $this->file,
52+
'line' => $this->line,
53+
);
54+
}
55+
}

0 commit comments

Comments
 (0)