mirror of
https://github.com/Seldaek/monolog.git
synced 2025-08-06 05:07:36 +02:00
Fix cycle detection within fibers (#1753)
* Fix cycle detection within fibers We keep a separate depth count per fiber. Fixes #1752. * Avoid additional call to Fiber::getCurrent() Suppresses phpstan errors, as they're false positives.
This commit is contained in:
@@ -168,6 +168,11 @@ class Logger implements LoggerInterface, ResettableInterface
|
||||
*/
|
||||
private $logDepth = 0;
|
||||
|
||||
/**
|
||||
* @var \WeakMap<\Fiber, int>|null Keeps track of depth inside fibers to prevent infinite logging loops
|
||||
*/
|
||||
private $fiberLogDepth;
|
||||
|
||||
/**
|
||||
* @var bool Whether to detect infinite logging loops
|
||||
*
|
||||
@@ -189,6 +194,13 @@ class Logger implements LoggerInterface, ResettableInterface
|
||||
$this->setHandlers($handlers);
|
||||
$this->processors = $processors;
|
||||
$this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC');
|
||||
|
||||
if (\PHP_VERSION_ID >= 80100) {
|
||||
// Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412
|
||||
/** @var \WeakMap<\Fiber, int> $fiberLogDepth */
|
||||
$fiberLogDepth = new \WeakMap();
|
||||
$this->fiberLogDepth = $fiberLogDepth;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
@@ -332,12 +344,21 @@ class Logger implements LoggerInterface, ResettableInterface
|
||||
}
|
||||
|
||||
if ($this->detectCycles) {
|
||||
$this->logDepth += 1;
|
||||
// @phpstan-ignore-next-line
|
||||
if (\PHP_VERSION_ID >= 80100 && $fiber = \Fiber::getCurrent()) {
|
||||
$this->fiberLogDepth[$fiber] = $this->fiberLogDepth[$fiber] ?? 0;
|
||||
$logDepth = ++$this->fiberLogDepth[$fiber];
|
||||
} else {
|
||||
$logDepth = ++$this->logDepth;
|
||||
}
|
||||
} else {
|
||||
$logDepth = 0;
|
||||
}
|
||||
if ($this->logDepth === 3) {
|
||||
|
||||
if ($logDepth === 3) {
|
||||
$this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.');
|
||||
return false;
|
||||
} elseif ($this->logDepth >= 5) { // log depth 4 is let through so we can log the warning above
|
||||
} elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -387,7 +408,12 @@ class Logger implements LoggerInterface, ResettableInterface
|
||||
}
|
||||
} finally {
|
||||
if ($this->detectCycles) {
|
||||
$this->logDepth--;
|
||||
if (isset($fiber)) {
|
||||
// @phpstan-ignore-next-line
|
||||
$this->fiberLogDepth[$fiber]--;
|
||||
} else {
|
||||
$this->logDepth--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -792,6 +792,58 @@ class LoggerTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertEquals($datetime->format('Y-m-d H:i:s'), $record['datetime']->format('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8.1
|
||||
*/
|
||||
public function testLogCycleDetectionWithFibersWithoutCycle()
|
||||
{
|
||||
$logger = new Logger(__METHOD__);
|
||||
|
||||
$fiberSuspendHandler = new FiberSuspendHandler();
|
||||
$testHandler = new TestHandler();
|
||||
|
||||
$logger->pushHandler($fiberSuspendHandler);
|
||||
$logger->pushHandler($testHandler);
|
||||
|
||||
$fibers = [];
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$fiber = new \Fiber(static function () use ($logger) {
|
||||
$logger->info('test');
|
||||
});
|
||||
|
||||
$fiber->start();
|
||||
|
||||
// We need to keep a reference here, because otherwise the fiber gets automatically cleaned up
|
||||
$fibers[] = $fiber;
|
||||
}
|
||||
|
||||
self::assertCount(10, $testHandler->getRecords());
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8.1
|
||||
*/
|
||||
public function testLogCycleDetectionWithFibersWithCycle()
|
||||
{
|
||||
$logger = new Logger(__METHOD__);
|
||||
|
||||
$fiberSuspendHandler = new FiberSuspendHandler();
|
||||
$loggingHandler = new LoggingHandler($logger);
|
||||
$testHandler = new TestHandler();
|
||||
|
||||
$logger->pushHandler($fiberSuspendHandler);
|
||||
$logger->pushHandler($loggingHandler);
|
||||
$logger->pushHandler($testHandler);
|
||||
|
||||
$fiber = new \Fiber(static function () use ($logger) {
|
||||
$logger->info('test');
|
||||
});
|
||||
|
||||
$fiber->start();
|
||||
|
||||
self::assertCount(3, $testHandler->getRecords());
|
||||
}
|
||||
}
|
||||
|
||||
class LoggingHandler implements HandlerInterface
|
||||
@@ -826,3 +878,27 @@ class LoggingHandler implements HandlerInterface
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FiberSuspendHandler implements HandlerInterface
|
||||
{
|
||||
public function isHandling(array $record): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handle(array $record): bool
|
||||
{
|
||||
\Fiber::suspend();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleBatch(array $records): void
|
||||
{
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user