1
0
mirror of https://github.com/Seldaek/monolog.git synced 2025-08-06 13:16:39 +02:00

Merge branch '1.x'

This commit is contained in:
Jordi Boggiano
2018-11-04 18:40:32 +01:00
27 changed files with 654 additions and 24 deletions

View File

@@ -19,6 +19,8 @@
* Fixed table row styling issues in HtmlFormatter
* Fixed RavenHandler dropping the message when logging exception
* Fixed WhatFailureGroupHandler skipping processors when using handleBatch
* Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors
and implement it where possible
### 1.23.0 (2017-06-19)

View File

@@ -5,6 +5,8 @@
help in some older codebases or for ease of use.
- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register
a Logger instance as an exception handler, error handler or fatal error handler.
- _SignalHandler_: The `Monolog\SignalHandler` class allows you to easily register
a Logger instance as a POSIX signal handler.
- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log
level is reached.
- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain

View File

@@ -13,6 +13,7 @@ namespace Monolog;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Monolog\Registry;
/**
* Monolog error handler
@@ -163,7 +164,7 @@ class ErrorHandler
$this->logger->log(
$level,
sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()),
sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()),
['exception' => $e]
);

View File

@@ -11,6 +11,7 @@
namespace Monolog\Formatter;
use Monolog\Utils;
use Throwable;
/**
@@ -166,7 +167,7 @@ class JsonFormatter extends NormalizerFormatter
protected function normalizeException(Throwable $e, int $depth = 0): array
{
$data = [
'class' => get_class($e),
'class' => Utils::getClass($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile().':'.$e->getLine(),

View File

@@ -11,6 +11,8 @@
namespace Monolog\Formatter;
use Monolog\Utils;
/**
* Formats incoming records into a one-line string
*
@@ -168,7 +170,7 @@ class LineFormatter extends NormalizerFormatter
private function formatException(\Throwable $e): string
{
$str = '[object] (' . get_class($e) . '(code: ' . $e->getCode() . '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')';
$str = '[object] (' . Utils::getClass($e) . '(code: ' . $e->getCode() . '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')';
if ($this->includeStacktraces) {
$str .= "\n[stacktrace]\n" . $e->getTraceAsString() . "\n";
}

View File

@@ -12,6 +12,7 @@
namespace Monolog\Formatter;
use MongoDB\BSON\UTCDateTime;
use Monolog\Utils;
/**
* Formats a record for use with the MongoDBHandler.
@@ -83,7 +84,7 @@ class MongoDBFormatter implements FormatterInterface
protected function formatObject($value, int $nestingLevel)
{
$objectVars = get_object_vars($value);
$objectVars['class'] = get_class($value);
$objectVars['class'] = Utils::getClass($value);
return $this->formatArray($objectVars, $nestingLevel);
}
@@ -91,7 +92,7 @@ class MongoDBFormatter implements FormatterInterface
protected function formatException(\Throwable $exception, int $nestingLevel)
{
$formattedException = [
'class' => get_class($exception),
'class' => Utils::getClass($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile() . ':' . $exception->getLine(),

View File

@@ -13,6 +13,7 @@ namespace Monolog\Formatter;
use Throwable;
use Monolog\DateTimeImmutable;
use Monolog\Utils;
/**
* Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
@@ -146,7 +147,7 @@ class NormalizerFormatter implements FormatterInterface
}
}
return [get_class($data) => $value];
return [Utils::getClass($data) => $value];
}
if (is_resource($data)) {
@@ -162,7 +163,7 @@ class NormalizerFormatter implements FormatterInterface
protected function normalizeException(Throwable $e, int $depth = 0)
{
$data = [
'class' => get_class($e),
'class' => Utils::getClass($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile().':'.$e->getLine(),
@@ -195,7 +196,7 @@ class NormalizerFormatter implements FormatterInterface
// as a class name to avoid any unexpected leak of sensitive information
$frame['args'] = array_map(function ($arg) {
if (is_object($arg) && !$arg instanceof \DateTimeInterface) {
return sprintf("[object] (%s)", get_class($arg));
return sprintf("[object] (%s)", Utils::getClass($arg));
}
return $arg;

View File

@@ -11,6 +11,8 @@
namespace Monolog\Handler;
use Monolog\ResettableInterface;
/**
* Base Handler class providing the Handler structure
*
@@ -19,7 +21,7 @@ namespace Monolog\Handler;
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*/
abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface
abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface
{
use ProcessableHandlerTrait;
use FormattableHandlerTrait;
@@ -48,4 +50,15 @@ abstract class AbstractProcessingHandler extends AbstractHandler implements Proc
* Writes the record down to the log of the implementing handler
*/
abstract protected function write(array $record): void;
public function reset()
{
$this->close();
foreach ($this->processors as $processor) {
if ($processor instanceof ResettableInterface) {
$processor->reset();
}
}
}
}

View File

@@ -70,14 +70,19 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
} elseif ($format === 'js') {
static::writeOutput(static::generateScript());
}
static::reset();
static::resetStatic();
}
}
public function reset()
{
self::resetStatic();
}
/**
* Forget all logged records
*/
public static function reset(): void
public static function resetStatic(): void
{
static::$records = [];
}

View File

@@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\ResettableInterface;
/**
* Buffers all records until closing the handler and then pass them as batch.
@@ -114,4 +115,13 @@ class BufferHandler extends AbstractHandler implements ProcessableHandlerInterfa
$this->bufferSize = 0;
$this->buffer = [];
}
public function reset()
{
parent::reset();
if ($this->handler instanceof ResettableInterface) {
$this->handler->reset();
}
}
}

View File

@@ -14,6 +14,7 @@ namespace Monolog\Handler;
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
use Monolog\Logger;
use Monolog\ResettableInterface;
/**
* Buffers all records until a certain level is reached
@@ -147,7 +148,14 @@ class FingersCrossedHandler extends Handler implements ProcessableHandlerInterfa
*/
public function reset()
{
parent::reset();
$this->buffer = array();
$this->buffering = true;
if ($this->handler instanceof ResettableInterface) {
$this->handler->reset();
}
}
/**

View File

@@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
use Monolog\ResettableInterface;
/**
* Forwards records to multiple handlers
@@ -90,6 +91,17 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface
}
}
public function reset()
{
parent::reset();
foreach ($this->handlers as $handler) {
if ($handler instanceof ResettableInterface) {
$handler->reset();
}
}
}
/**
* {@inheritdoc}
*/

View File

@@ -11,6 +11,7 @@
namespace Monolog\Handler;
use Monolog\ResettableInterface;
use Monolog\Formatter\FormatterInterface;
/**
@@ -30,7 +31,7 @@ use Monolog\Formatter\FormatterInterface;
*
* @author Alexey Karapetov <alexey@karapetov.com>
*/
class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface
class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface
{
/**
* @var HandlerInterface
@@ -127,4 +128,11 @@ class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, F
throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class);
}
public function reset()
{
if ($this->handler instanceof ResettableInterface) {
return $this->handler->reset();
}
}
}

View File

@@ -18,7 +18,7 @@ use Raven_Client;
/**
* Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server
* using raven-php (https://github.com/getsentry/raven-php)
* using sentry-php (https://github.com/getsentry/sentry-php)
*
* @author Marc Abramowitz <marc@marc-abramowitz.com>
*/

View File

@@ -25,7 +25,7 @@ use Throwable;
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class Logger implements LoggerInterface
class Logger implements LoggerInterface, ResettableInterface
{
/**
* Detailed debug information
@@ -333,6 +333,21 @@ class Logger implements LoggerInterface
return true;
}
public function reset()
{
foreach ($this->handlers as $handler) {
if ($handler instanceof ResettableInterface) {
$handler->reset();
}
}
foreach ($this->processors as $processor) {
if ($processor instanceof ResettableInterface) {
$processor->reset();
}
}
}
/**
* Gets all supported logging levels.
*

View File

@@ -11,6 +11,8 @@
namespace Monolog\Processor;
use Monolog\Utils;
/**
* Processes a record's message according to PSR-3 rules
*
@@ -66,7 +68,7 @@ class PsrLogMessageProcessor implements ProcessorInterface
$replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE);
}
} elseif (is_object($val)) {
$replacements[$placeholder] = '[object '.get_class($val).']';
$replacements[$placeholder] = '[object '.Utils::getClass($val).']';
} elseif (is_array($val)) {
$replacements[$placeholder] = 'array'.@json_encode($val);
} else {

View File

@@ -11,12 +11,14 @@
namespace Monolog\Processor;
use Monolog\ResettableInterface;
/**
* Adds a unique identifier into records
*
* @author Simon Mönch <sm@webfactory.de>
*/
class UidProcessor implements ProcessorInterface
class UidProcessor implements ProcessorInterface, ResettableInterface
{
private $uid;
@@ -26,7 +28,7 @@ class UidProcessor implements ProcessorInterface
throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32');
}
$this->uid = substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length);
$this->uid = $this->generateUid($length);
}
public function __invoke(array $record): array
@@ -43,4 +45,14 @@ class UidProcessor implements ProcessorInterface
{
return $this->uid;
}
public function reset()
{
$this->uid = $this->generateUid(strlen($this->uid));
}
private function generateUid($length)
{
return substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length);
}
}

View File

@@ -130,4 +130,14 @@ class Registry
{
return self::getInstance($name);
}
/**
* @internal
*/
public function getClass($object)
{
$class = \get_class($object);
return 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
/**
* Handler or Processor implementing this interface will be reset when Logger::reset() is called.
*
* Resetting an Handler or a Processor usually means cleaning all buffers or
* resetting in its internal state. This should also generally close() the handler.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
interface ResettableInterface
{
public function reset();
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use ReflectionExtension;
/**
* Monolog POSIX signal handler
*
* @author Robert Gust-Bardon <robert@gust-bardon.org>
*/
class SignalHandler
{
private $logger;
private $previousSignalHandler = array();
private $signalLevelMap = array();
private $signalRestartSyscalls = array();
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true)
{
if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) {
return $this;
}
if ($callPrevious) {
if (function_exists('pcntl_signal_get_handler')) {
$handler = pcntl_signal_get_handler($signo);
if ($handler === false) {
return $this;
}
$this->previousSignalHandler[$signo] = $handler;
} else {
$this->previousSignalHandler[$signo] = true;
}
} else {
unset($this->previousSignalHandler[$signo]);
}
$this->signalLevelMap[$signo] = $level;
$this->signalRestartSyscalls[$signo] = $restartSyscalls;
if (function_exists('pcntl_async_signals') && $async !== null) {
pcntl_async_signals($async);
}
pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls);
return $this;
}
public function handleSignal($signo, array $siginfo = null)
{
static $signals = array();
if (!$signals && extension_loaded('pcntl')) {
$pcntl = new ReflectionExtension('pcntl');
$constants = $pcntl->getConstants();
if (!$constants) {
// HHVM 3.24.2 returns an empty array.
$constants = get_defined_constants(true);
$constants = $constants['Core'];
}
foreach ($constants as $name => $value) {
if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) {
$signals[$value] = $name;
}
}
unset($constants);
}
$level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL;
$signal = isset($signals[$signo]) ? $signals[$signo] : $signo;
$context = isset($siginfo) ? $siginfo : array();
$this->logger->log($level, sprintf('Program received signal %s', $signal), $context);
if (!isset($this->previousSignalHandler[$signo])) {
return;
}
if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) {
if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch')
&& extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) {
$restartSyscalls = isset($this->restartSyscalls[$signo]) ? $this->restartSyscalls[$signo] : true;
pcntl_signal($signo, SIG_DFL, $restartSyscalls);
pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset);
posix_kill(posix_getpid(), $signo);
pcntl_signal_dispatch();
pcntl_sigprocmask(SIG_SETMASK, $oldset);
pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls);
}
} elseif (is_callable($this->previousSignalHandler[$signo])) {
if (PHP_VERSION_ID >= 70100) {
$this->previousSignalHandler[$signo]($signo, $siginfo);
} else {
$this->previousSignalHandler[$signo]($signo);
}
}
}
}

25
src/Monolog/Utils.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
class Utils
{
/**
* @internal
*/
public function getClass($object)
{
$class = \get_class($object);
return 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
}
}

View File

@@ -21,7 +21,7 @@ class BrowserConsoleHandlerTest extends TestCase
{
protected function setUp()
{
BrowserConsoleHandler::reset();
BrowserConsoleHandler::resetStatic();
}
protected function generateScript()

View File

@@ -21,7 +21,7 @@ class ChromePHPHandlerTest extends TestCase
{
protected function setUp()
{
TestChromePHPHandler::reset();
TestChromePHPHandler::resetStatic();
$_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; Chrome/1.0';
}
@@ -136,7 +136,7 @@ class TestChromePHPHandler extends ChromePHPHandler
{
protected $headers = [];
public static function reset()
public static function resetStatic()
{
self::$initialized = false;
self::$overflowed = false;

View File

@@ -58,7 +58,7 @@ class FingersCrossedHandlerTest extends TestCase
* @covers Monolog\Handler\FingersCrossedHandler::activate
* @covers Monolog\Handler\FingersCrossedHandler::reset
*/
public function testHandleRestartBufferingAfterReset()
public function testHandleResetBufferingAfterReset()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test);
@@ -76,7 +76,7 @@ class FingersCrossedHandlerTest extends TestCase
* @covers Monolog\Handler\FingersCrossedHandler::handle
* @covers Monolog\Handler\FingersCrossedHandler::activate
*/
public function testHandleRestartBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()
public function testHandleResetBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false);

View File

@@ -21,7 +21,7 @@ class FirePHPHandlerTest extends TestCase
{
public function setUp()
{
TestFirePHPHandler::reset();
TestFirePHPHandler::resetStatic();
$_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; FirePHP/1.0';
}
@@ -77,7 +77,7 @@ class TestFirePHPHandler extends FirePHPHandler
{
protected $headers = [];
public static function reset()
public static function resetStatic()
{
self::$initialized = false;
self::$sendHeaders = true;

View File

@@ -635,4 +635,77 @@ class LoggerTest extends \PHPUnit\Framework\TestCase
$logger->pushHandler($handler);
$logger->info('test');
}
public function testReset()
{
$logger = new Logger('app');
$testHandler = new Handler\TestHandler();
$bufferHandler = new Handler\BufferHandler($testHandler);
$groupHandler = new Handler\GroupHandler(array($bufferHandler));
$fingersCrossedHandler = new Handler\FingersCrossedHandler($groupHandler);
$logger->pushHandler($fingersCrossedHandler);
$processorUid1 = new Processor\UidProcessor(10);
$uid1 = $processorUid1->getUid();
$groupHandler->pushProcessor($processorUid1);
$processorUid2 = new Processor\UidProcessor(5);
$uid2 = $processorUid2->getUid();
$logger->pushProcessor($processorUid2);
$getProperty = function ($object, $property) {
$reflectionProperty = new \ReflectionProperty(get_class($object), $property);
$reflectionProperty->setAccessible(true);
return $reflectionProperty->getValue($object);
};
$that = $this;
$assertBufferOfBufferHandlerEmpty = function () use ($getProperty, $bufferHandler, $that) {
$that->assertEmpty($getProperty($bufferHandler, 'buffer'));
};
$assertBuffersEmpty = function() use ($assertBufferOfBufferHandlerEmpty, $getProperty, $fingersCrossedHandler, $that) {
$assertBufferOfBufferHandlerEmpty();
$that->assertEmpty($getProperty($fingersCrossedHandler, 'buffer'));
};
$logger->debug('debug');
$logger->reset();
$assertBuffersEmpty();
$this->assertFalse($testHandler->hasDebugRecords());
$this->assertFalse($testHandler->hasErrorRecords());
$this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
$this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
$logger->debug('debug');
$logger->error('error');
$logger->reset();
$assertBuffersEmpty();
$this->assertTrue($testHandler->hasDebugRecords());
$this->assertTrue($testHandler->hasErrorRecords());
$this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
$this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
$logger->info('info');
$this->assertNotEmpty($getProperty($fingersCrossedHandler, 'buffer'));
$assertBufferOfBufferHandlerEmpty();
$this->assertFalse($testHandler->hasInfoRecords());
$logger->reset();
$assertBuffersEmpty();
$this->assertFalse($testHandler->hasInfoRecords());
$this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
$this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
$logger->notice('notice');
$logger->emergency('emergency');
$logger->reset();
$assertBuffersEmpty();
$this->assertFalse($testHandler->hasInfoRecords());
$this->assertTrue($testHandler->hasNoticeRecords());
$this->assertTrue($testHandler->hasEmergencyRecords());
$this->assertNotSame($uid1, $processorUid1->getUid());
$this->assertNotSame($uid2, $processorUid2->getUid());
}
}

View File

@@ -0,0 +1,287 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\TestHandler;
use Psr\Log\LogLevel;
/**
* @author Robert Gust-Bardon <robert@gust-bardon.org>
* @covers Monolog\SignalHandler
*/
class SignalHandlerTest extends TestCase
{
private $asyncSignalHandling;
private $blockedSignals;
private $signalHandlers;
protected function setUp()
{
$this->signalHandlers = array();
if (extension_loaded('pcntl')) {
if (function_exists('pcntl_async_signals')) {
$this->asyncSignalHandling = pcntl_async_signals();
}
if (function_exists('pcntl_sigprocmask')) {
pcntl_sigprocmask(SIG_BLOCK, array(), $this->blockedSignals);
}
}
}
protected function tearDown()
{
if ($this->asyncSignalHandling !== null) {
pcntl_async_signals($this->asyncSignalHandling);
}
if ($this->blockedSignals !== null) {
pcntl_sigprocmask(SIG_SETMASK, $this->blockedSignals);
}
if ($this->signalHandlers) {
pcntl_signal_dispatch();
foreach ($this->signalHandlers as $signo => $handler) {
pcntl_signal($signo, $handler);
}
}
}
private function setSignalHandler($signo, $handler = SIG_DFL) {
if (function_exists('pcntl_signal_get_handler')) {
$this->signalHandlers[$signo] = pcntl_signal_get_handler($signo);
} else {
$this->signalHandlers[$signo] = SIG_DFL;
}
$this->assertTrue(pcntl_signal($signo, $handler));
}
public function testHandleSignal()
{
$logger = new Logger('test', array($handler = new TestHandler));
$errHandler = new SignalHandler($logger);
$signo = 2; // SIGINT.
$siginfo = array('signo' => $signo, 'errno' => 0, 'code' => 0);
$errHandler->handleSignal($signo, $siginfo);
$this->assertCount(1, $handler->getRecords());
$this->assertTrue($handler->hasCriticalRecords());
$records = $handler->getRecords();
$this->assertSame($siginfo, $records[0]['context']);
}
/**
* @depends testHandleSignal
* @requires extension pcntl
* @requires extension posix
* @requires function pcntl_signal
* @requires function pcntl_signal_dispatch
* @requires function posix_getpid
* @requires function posix_kill
*/
public function testRegisterSignalHandler()
{
// SIGCONT and SIGURG should be ignored by default.
if (!defined('SIGCONT') || !defined('SIGURG')) {
$this->markTestSkipped('This test requires the SIGCONT and SIGURG pcntl constants.');
}
$this->setSignalHandler(SIGCONT, SIG_IGN);
$this->setSignalHandler(SIGURG, SIG_IGN);
$logger = new Logger('test', array($handler = new TestHandler));
$errHandler = new SignalHandler($logger);
$pid = posix_getpid();
$this->assertTrue(posix_kill($pid, SIGURG));
$this->assertTrue(pcntl_signal_dispatch());
$this->assertCount(0, $handler->getRecords());
$errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, false);
$this->assertTrue(posix_kill($pid, SIGCONT));
$this->assertTrue(pcntl_signal_dispatch());
$this->assertCount(0, $handler->getRecords());
$this->assertTrue(posix_kill($pid, SIGURG));
$this->assertTrue(pcntl_signal_dispatch());
$this->assertCount(1, $handler->getRecords());
$this->assertTrue($handler->hasInfoThatContains('SIGURG'));
}
/**
* @dataProvider defaultPreviousProvider
* @depends testRegisterSignalHandler
* @requires function pcntl_fork
* @requires function pcntl_sigprocmask
* @requires function pcntl_waitpid
*/
public function testRegisterDefaultPreviousSignalHandler($signo, $callPrevious, $expected)
{
$this->setSignalHandler($signo, SIG_DFL);
$path = tempnam(sys_get_temp_dir(), 'monolog-');
$this->assertNotFalse($path);
$pid = pcntl_fork();
if ($pid === 0) { // Child.
$streamHandler = new StreamHandler($path);
$streamHandler->setFormatter($this->getIdentityFormatter());
$logger = new Logger('test', array($streamHandler));
$errHandler = new SignalHandler($logger);
$errHandler->registerSignalHandler($signo, LogLevel::INFO, $callPrevious, false, false);
pcntl_sigprocmask(SIG_SETMASK, array(SIGCONT));
posix_kill(posix_getpid(), $signo);
pcntl_signal_dispatch();
// If $callPrevious is true, SIGINT should terminate by this line.
pcntl_sigprocmask(SIG_BLOCK, array(), $oldset);
file_put_contents($path, implode(' ', $oldset), FILE_APPEND);
posix_kill(posix_getpid(), $signo);
pcntl_signal_dispatch();
exit();
}
$this->assertNotSame(-1, $pid);
$this->assertNotSame(-1, pcntl_waitpid($pid, $status));
$this->assertNotSame(-1, $status);
$this->assertSame($expected, file_get_contents($path));
}
public function defaultPreviousProvider()
{
if (!defined('SIGCONT') || !defined('SIGINT') || !defined('SIGURG')) {
return array();
}
return array(
array(SIGINT, false, 'Program received signal SIGINT'.SIGCONT.'Program received signal SIGINT'),
array(SIGINT, true, 'Program received signal SIGINT'),
array(SIGURG, false, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'),
array(SIGURG, true, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'),
);
}
/**
* @dataProvider callablePreviousProvider
* @depends testRegisterSignalHandler
* @requires function pcntl_signal_get_handler
*/
public function testRegisterCallablePreviousSignalHandler($callPrevious)
{
$this->setSignalHandler(SIGURG, SIG_IGN);
$logger = new Logger('test', array($handler = new TestHandler));
$errHandler = new SignalHandler($logger);
$previousCalled = 0;
pcntl_signal(SIGURG, function ($signo, array $siginfo = null) use (&$previousCalled) {
++$previousCalled;
});
$errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, $callPrevious, false, false);
$this->assertTrue(posix_kill(posix_getpid(), SIGURG));
$this->assertTrue(pcntl_signal_dispatch());
$this->assertCount(1, $handler->getRecords());
$this->assertTrue($handler->hasInfoThatContains('SIGURG'));
$this->assertSame($callPrevious ? 1 : 0, $previousCalled);
}
public function callablePreviousProvider()
{
return array(
array(false),
array(true),
);
}
/**
* @dataProvider restartSyscallsProvider
* @depends testRegisterDefaultPreviousSignalHandler
* @requires function pcntl_fork
* @requires function pcntl_waitpid
*/
public function testRegisterSyscallRestartingSignalHandler($restartSyscalls)
{
$this->setSignalHandler(SIGURG, SIG_IGN);
$parentPid = posix_getpid();
$microtime = microtime(true);
$pid = pcntl_fork();
if ($pid === 0) { // Child.
usleep(100000);
posix_kill($parentPid, SIGURG);
usleep(100000);
exit();
}
$this->assertNotSame(-1, $pid);
$logger = new Logger('test', array($handler = new TestHandler));
$errHandler = new SignalHandler($logger);
$errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, $restartSyscalls, false);
if ($restartSyscalls) {
// pcntl_wait is expected to be restarted after the signal handler.
$this->assertNotSame(-1, pcntl_waitpid($pid, $status));
} else {
// pcntl_wait is expected to be interrupted when the signal handler is invoked.
$this->assertSame(-1, pcntl_waitpid($pid, $status));
}
$this->assertSame($restartSyscalls, microtime(true) - $microtime > 0.15);
$this->assertTrue(pcntl_signal_dispatch());
$this->assertCount(1, $handler->getRecords());
if ($restartSyscalls) {
// The child has already exited.
$this->assertSame(-1, pcntl_waitpid($pid, $status));
} else {
// The child has not exited yet.
$this->assertNotSame(-1, pcntl_waitpid($pid, $status));
}
}
public function restartSyscallsProvider()
{
return array(
array(false),
array(true),
array(false),
array(true),
);
}
/**
* @dataProvider asyncProvider
* @depends testRegisterDefaultPreviousSignalHandler
* @requires function pcntl_async_signals
*/
public function testRegisterAsyncSignalHandler($initialAsync, $desiredAsync, $expectedBefore, $expectedAfter)
{
$this->setSignalHandler(SIGURG, SIG_IGN);
pcntl_async_signals($initialAsync);
$logger = new Logger('test', array($handler = new TestHandler));
$errHandler = new SignalHandler($logger);
$errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, $desiredAsync);
$this->assertTrue(posix_kill(posix_getpid(), SIGURG));
$this->assertCount($expectedBefore, $handler->getRecords());
$this->assertTrue(pcntl_signal_dispatch());
$this->assertCount($expectedAfter, $handler->getRecords());
}
public function asyncProvider()
{
return array(
array(false, false, 0, 1),
array(false, null, 0, 1),
array(false, true, 1, 1),
array(true, false, 0, 1),
array(true, null, 1, 1),
array(true, true, 1, 1),
);
}
}