mirror of
https://github.com/Seldaek/monolog.git
synced 2025-08-03 11:47:38 +02:00
Move POSIX signal handling to SignalHandler*
Suggested by Helmut Hummel (@helhum).
This commit is contained in:
@@ -4,8 +4,9 @@
|
|||||||
can then statically access from anywhere. It is not really a best practice but can
|
can then statically access from anywhere. It is not really a best practice but can
|
||||||
help in some older codebases or for ease of use.
|
help in some older codebases or for ease of use.
|
||||||
- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register
|
- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register
|
||||||
a Logger instance as an exception handler, error handler, fatal error handler or
|
a Logger instance as an exception handler, error handler or fatal error handler.
|
||||||
signal 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
|
- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log
|
||||||
level is reached.
|
level is reached.
|
||||||
- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain
|
- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain
|
||||||
|
@@ -14,12 +14,11 @@ namespace Monolog;
|
|||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
use Monolog\Handler\AbstractHandler;
|
use Monolog\Handler\AbstractHandler;
|
||||||
use ReflectionExtension;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monolog error handler
|
* Monolog error handler
|
||||||
*
|
*
|
||||||
* A facility to enable logging of runtime errors, exceptions, fatal errors and signals.
|
* A facility to enable logging of runtime errors, exceptions and fatal errors.
|
||||||
*
|
*
|
||||||
* Quick setup: <code>ErrorHandler::register($logger);</code>
|
* Quick setup: <code>ErrorHandler::register($logger);</code>
|
||||||
*
|
*
|
||||||
@@ -42,10 +41,6 @@ class ErrorHandler
|
|||||||
private $lastFatalTrace;
|
private $lastFatalTrace;
|
||||||
private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
|
private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
|
||||||
|
|
||||||
private $previousSignalHandler = array();
|
|
||||||
private $signalLevelMap = array();
|
|
||||||
private $signalRestartSyscalls = array();
|
|
||||||
|
|
||||||
public function __construct(LoggerInterface $logger)
|
public function __construct(LoggerInterface $logger)
|
||||||
{
|
{
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
@@ -110,37 +105,6 @@ class ErrorHandler
|
|||||||
$this->hasFatalErrorHandler = true;
|
$this->hasFatalErrorHandler = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function defaultErrorLevelMap()
|
protected function defaultErrorLevelMap()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
@@ -234,55 +198,6 @@ class ErrorHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 >= 71000) {
|
|
||||||
$this->previousSignalHandler[$signo]($signo, $siginfo);
|
|
||||||
} else {
|
|
||||||
$this->previousSignalHandler[$signo]($signo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function codeToString($code)
|
private static function codeToString($code)
|
||||||
{
|
{
|
||||||
switch ($code) {
|
switch ($code) {
|
||||||
|
115
src/Monolog/SignalHandler.php
Normal file
115
src/Monolog/SignalHandler.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -11,55 +11,10 @@
|
|||||||
|
|
||||||
namespace Monolog;
|
namespace Monolog;
|
||||||
|
|
||||||
use Monolog\Handler\StreamHandler;
|
|
||||||
use Monolog\Handler\TestHandler;
|
use Monolog\Handler\TestHandler;
|
||||||
use Psr\Log\LogLevel;
|
|
||||||
|
|
||||||
class ErrorHandlerTest extends TestCase
|
class ErrorHandlerTest extends \PHPUnit_Framework_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 testHandleError()
|
public function testHandleError()
|
||||||
{
|
{
|
||||||
$logger = new Logger('test', array($handler = new TestHandler));
|
$logger = new Logger('test', array($handler = new TestHandler));
|
||||||
@@ -73,225 +28,4 @@ class ErrorHandlerTest extends TestCase
|
|||||||
$this->assertCount(2, $handler->getRecords());
|
$this->assertCount(2, $handler->getRecords());
|
||||||
$this->assertTrue($handler->hasEmergencyRecords());
|
$this->assertTrue($handler->hasEmergencyRecords());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandleSignal()
|
|
||||||
{
|
|
||||||
$logger = new Logger('test', array($handler = new TestHandler));
|
|
||||||
$errHandler = new ErrorHandler($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 ErrorHandler($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 ErrorHandler($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 ErrorHandler($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 ErrorHandler($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 ErrorHandler($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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
287
tests/Monolog/SignalHandlerTest.php
Normal file
287
tests/Monolog/SignalHandlerTest.php
Normal 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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user