1<?php declare(strict_types=1); 2 3/* 4 * This file is part of the Monolog package. 5 * 6 * (c) Jordi Boggiano <j.boggiano@seld.be> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Monolog; 13 14use Psr\Log\LoggerInterface; 15use Psr\Log\LogLevel; 16use ReflectionExtension; 17 18/** 19 * Monolog POSIX signal handler 20 * 21 * @author Robert Gust-Bardon <robert@gust-bardon.org> 22 * 23 * @phpstan-import-type Level from \Monolog\Logger 24 * @phpstan-import-type LevelName from \Monolog\Logger 25 */ 26class SignalHandler 27{ 28 /** @var LoggerInterface */ 29 private $logger; 30 31 /** @var array<int, callable|string|int> SIG_DFL, SIG_IGN or previous callable */ 32 private $previousSignalHandler = []; 33 /** @var array<int, int> */ 34 private $signalLevelMap = []; 35 /** @var array<int, bool> */ 36 private $signalRestartSyscalls = []; 37 38 public function __construct(LoggerInterface $logger) 39 { 40 $this->logger = $logger; 41 } 42 43 /** 44 * @param int|string $level Level or level name 45 * @param bool $callPrevious 46 * @param bool $restartSyscalls 47 * @param bool|null $async 48 * @return $this 49 * 50 * @phpstan-param Level|LevelName|LogLevel::* $level 51 */ 52 public function registerSignalHandler(int $signo, $level = LogLevel::CRITICAL, bool $callPrevious = true, bool $restartSyscalls = true, ?bool $async = true): self 53 { 54 if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { 55 return $this; 56 } 57 58 $level = Logger::toMonologLevel($level); 59 60 if ($callPrevious) { 61 $handler = pcntl_signal_get_handler($signo); 62 $this->previousSignalHandler[$signo] = $handler; 63 } else { 64 unset($this->previousSignalHandler[$signo]); 65 } 66 $this->signalLevelMap[$signo] = $level; 67 $this->signalRestartSyscalls[$signo] = $restartSyscalls; 68 69 if ($async !== null) { 70 pcntl_async_signals($async); 71 } 72 73 pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); 74 75 return $this; 76 } 77 78 /** 79 * @param mixed $siginfo 80 */ 81 public function handleSignal(int $signo, $siginfo = null): void 82 { 83 static $signals = []; 84 85 if (!$signals && extension_loaded('pcntl')) { 86 $pcntl = new ReflectionExtension('pcntl'); 87 // HHVM 3.24.2 returns an empty array. 88 foreach ($pcntl->getConstants() ?: get_defined_constants(true)['Core'] as $name => $value) { 89 if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { 90 $signals[$value] = $name; 91 } 92 } 93 } 94 95 $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL; 96 $signal = $signals[$signo] ?? $signo; 97 $context = $siginfo ?? []; 98 $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); 99 100 if (!isset($this->previousSignalHandler[$signo])) { 101 return; 102 } 103 104 if ($this->previousSignalHandler[$signo] === SIG_DFL) { 105 if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') 106 && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill') 107 ) { 108 $restartSyscalls = $this->signalRestartSyscalls[$signo] ?? true; 109 pcntl_signal($signo, SIG_DFL, $restartSyscalls); 110 pcntl_sigprocmask(SIG_UNBLOCK, [$signo], $oldset); 111 posix_kill(posix_getpid(), $signo); 112 pcntl_signal_dispatch(); 113 pcntl_sigprocmask(SIG_SETMASK, $oldset); 114 pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); 115 } 116 } elseif (is_callable($this->previousSignalHandler[$signo])) { 117 $this->previousSignalHandler[$signo]($signo, $siginfo); 118 } 119 } 120} 121