Skip to content

Commit 1a79a13

Browse files
bug symfony#29869 [Debug][ErrorHandler] Preserve our error handler when a logger sets another one (fancyweb)
This PR was merged into the 3.4 branch. Discussion ---------- [Debug][ErrorHandler] Preserve our error handler when a logger sets another one | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - When logging errors handled by the `ErrorHandler::handleError()` method, the logger can temporarily set its own custom error handler. This is for example the case of `Monolog` in the `StreamHandler` class (cf https://github.com/Seldaek/monolog/blob/ebb804e432e8fe0fe96828f30d89c45581d36d07/src/Monolog/Handler/StreamHandler.php#L101). However, when the previous error handler is restored by the logger, it "skips" the real previous handler (the `ErrorHandler::handleError()` one) in the pile and goes back directly to the one before. I guess this is because the `restore_error_handler()` call is technically done in the error handler itself, so it logically restore it to the one before and not to itself. Here is an easy small example that shows the PHP behavior : https://3v4l.org/4OZNZ The only solution I have found to fix it is to set our error handler everytime an error is logged. Here are the things I discovered while trying to find a cleaner fix : - Setting the same error handler in the error handler itself doesn't actually add it to the pile. This is why adding a check is useless. - Checking if the logger modified the error handler is impossible anyway : to get the current error handler, you need to set a new one temporarirly and then revert it. However, when you revert it by calling `restore_error_handler()` you end up having the same problem you are trying to fix... - Also trying to get the current error handler in the error handler itself will return NULL if it is itself. Commits ------- b979fff [Debug][ErrorHandler] Preserve our error handler when a logger set another one
2 parents e3b010f + b979fff commit 1a79a13

File tree

3 files changed

+62
-0
lines changed

3 files changed

+62
-0
lines changed

src/Symfony/Component/Debug/ErrorHandler.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,10 @@ public function handleError($type, $message, $file, $line)
523523
$this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []);
524524
} finally {
525525
$this->isRecursive = false;
526+
527+
if (!\defined('HHVM_VERSION')) {
528+
set_error_handler([$this, __FUNCTION__]);
529+
}
526530
}
527531
}
528532

src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212
namespace Symfony\Component\Debug\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Psr\Log\LoggerInterface;
1516
use Psr\Log\LogLevel;
17+
use Psr\Log\NullLogger;
1618
use Symfony\Component\Debug\BufferingLogger;
1719
use Symfony\Component\Debug\ErrorHandler;
1820
use Symfony\Component\Debug\Exception\SilencedErrorContext;
21+
use Symfony\Component\Debug\Tests\Fixtures\LoggerThatSetAnErrorHandler;
1922

2023
/**
2124
* ErrorHandlerTest.
@@ -321,6 +324,8 @@ public function testHandleDeprecation()
321324
$handler = new ErrorHandler();
322325
$handler->setDefaultLogger($logger);
323326
@$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, []);
327+
328+
restore_error_handler();
324329
}
325330

326331
/**
@@ -583,4 +588,43 @@ public function testCustomExceptionHandler()
583588

584589
$handler->handleException(new \Exception());
585590
}
591+
592+
/**
593+
* @dataProvider errorHandlerIsNotLostWhenLoggingProvider
594+
*/
595+
public function testErrorHandlerIsNotLostWhenLogging($customErrorHandlerHasBeenPreviouslyDefined, LoggerInterface $logger)
596+
{
597+
try {
598+
if ($customErrorHandlerHasBeenPreviouslyDefined) {
599+
set_error_handler('count');
600+
}
601+
602+
$handler = ErrorHandler::register();
603+
$handler->setDefaultLogger($logger);
604+
605+
@trigger_error('foo', E_USER_DEPRECATED);
606+
@trigger_error('bar', E_USER_DEPRECATED);
607+
608+
$this->assertSame([$handler, 'handleError'], set_error_handler('var_dump'));
609+
610+
restore_error_handler();
611+
612+
if ($customErrorHandlerHasBeenPreviouslyDefined) {
613+
restore_error_handler();
614+
}
615+
} finally {
616+
restore_error_handler();
617+
restore_exception_handler();
618+
}
619+
}
620+
621+
public function errorHandlerIsNotLostWhenLoggingProvider()
622+
{
623+
return [
624+
[false, new NullLogger()],
625+
[true, new NullLogger()],
626+
[false, new LoggerThatSetAnErrorHandler()],
627+
[true, new LoggerThatSetAnErrorHandler()],
628+
];
629+
}
586630
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
use Psr\Log\AbstractLogger;
6+
7+
class LoggerThatSetAnErrorHandler extends AbstractLogger
8+
{
9+
public function log($level, $message, array $context = [])
10+
{
11+
set_error_handler('is_string');
12+
restore_error_handler();
13+
}
14+
}

0 commit comments

Comments
 (0)