diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 04ba0f33..6ef80e8b 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 6 + level: 8 treatPhpDocTypesAsCertain: false reportUnmatchedIgnoredErrors: false @@ -19,7 +19,16 @@ parameters: paths: - src/Monolog/Handler/LogglyHandler.php + # blocked until we only support php8+ + - '#Parameter \#1 \$socket of function (socket_close|socket_sendto|socket_send) expects Socket, resource\|Socket(\|null)? given\.#' + - '#Parameter \#1 \$handle of function (curl_exec|curl_close|curl_error|curl_errno|curl_setopt) expects CurlHandle, CurlHandle\|resource(\|null)? given\.#' + # blocked by https://github.com/phpstan/phpstan/issues/5091 - '#has unknown class Monolog\\Handler\\Record#' - - '#::processRecord#' + - '#::processRecord\(\) should return array#' + - '#::processRecord\(\) has invalid type#' + - '#::processRecord\(\) return type has no value type#' + - '#::processRecord\(\) has parameter \$record with no value type#' + - '#::popProcessor\(\) should return callable#' + - '#Parameter \#1 \$ of callable \(callable\(Monolog\\Handler\\Record\): Monolog\\Handler\\Record\)#' - '#is incompatible with native type array.#' diff --git a/src/Monolog/Formatter/ElasticaFormatter.php b/src/Monolog/Formatter/ElasticaFormatter.php index 9504d89c..3939c1c5 100644 --- a/src/Monolog/Formatter/ElasticaFormatter.php +++ b/src/Monolog/Formatter/ElasticaFormatter.php @@ -65,6 +65,7 @@ class ElasticaFormatter extends NormalizerFormatter */ public function getType(): string { + /** @phpstan-ignore-next-line */ return $this->type; } @@ -78,6 +79,7 @@ class ElasticaFormatter extends NormalizerFormatter $document = new Document(); $document->setData($record); if (method_exists($document, 'setType')) { + /** @phpstan-ignore-next-line */ $document->setType($this->type); } $document->setIndex($this->index); diff --git a/src/Monolog/Formatter/GelfMessageFormatter.php b/src/Monolog/Formatter/GelfMessageFormatter.php index 3ca84c33..a563a1ea 100644 --- a/src/Monolog/Formatter/GelfMessageFormatter.php +++ b/src/Monolog/Formatter/GelfMessageFormatter.php @@ -20,6 +20,8 @@ use Monolog\Utils; * @see http://docs.graylog.org/en/latest/pages/gelf.html * * @author Matt Lehner + * + * @phpstan-import-type Level from \Monolog\Logger */ class GelfMessageFormatter extends NormalizerFormatter { @@ -49,6 +51,8 @@ class GelfMessageFormatter extends NormalizerFormatter * Translates Monolog log levels to Graylog2 log priorities. * * @var array + * + * @phpstan-var array */ private $logLevels = [ Logger::DEBUG => 7, @@ -65,7 +69,7 @@ class GelfMessageFormatter extends NormalizerFormatter { parent::__construct('U.u'); - $this->systemName = (is_null($systemName) || $systemName === '') ? gethostname() : $systemName; + $this->systemName = (is_null($systemName) || $systemName === '') ? (string) gethostname() : $systemName; $this->extraPrefix = is_null($extraPrefix) ? '' : $extraPrefix; $this->contextPrefix = $contextPrefix; @@ -73,15 +77,18 @@ class GelfMessageFormatter extends NormalizerFormatter } /** - * {@inheritdoc} + * {@inheritDoc} */ public function format(array $record): Message { + $context = $extra = []; if (isset($record['context'])) { - $record['context'] = parent::format($record['context']); + /** @var mixed[] $context */ + $context = parent::normalize($record['context']); } if (isset($record['extra'])) { - $record['extra'] = parent::format($record['extra']); + /** @var mixed[] $extra */ + $extra = parent::normalize($record['extra']); } if (!isset($record['datetime'], $record['message'], $record['level'])) { @@ -105,31 +112,31 @@ class GelfMessageFormatter extends NormalizerFormatter if (isset($record['channel'])) { $message->setFacility($record['channel']); } - if (isset($record['extra']['line'])) { - $message->setLine($record['extra']['line']); - unset($record['extra']['line']); + if (isset($extra['line'])) { + $message->setLine($extra['line']); + unset($extra['line']); } - if (isset($record['extra']['file'])) { - $message->setFile($record['extra']['file']); - unset($record['extra']['file']); + if (isset($extra['file'])) { + $message->setFile($extra['file']); + unset($extra['file']); } - foreach ($record['extra'] as $key => $val) { + foreach ($extra as $key => $val) { $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); $len = strlen($this->extraPrefix . $key . $val); if ($len > $this->maxLength) { - $message->setAdditional($this->extraPrefix . $key, Utils::substr($val, 0, $this->maxLength)); + $message->setAdditional($this->extraPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); continue; } $message->setAdditional($this->extraPrefix . $key, $val); } - foreach ($record['context'] as $key => $val) { + foreach ($context as $key => $val) { $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); $len = strlen($this->contextPrefix . $key . $val); if ($len > $this->maxLength) { - $message->setAdditional($this->contextPrefix . $key, Utils::substr($val, 0, $this->maxLength)); + $message->setAdditional($this->contextPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); continue; } @@ -137,8 +144,8 @@ class GelfMessageFormatter extends NormalizerFormatter } /** @phpstan-ignore-next-line */ - if (null === $message->getFile() && isset($record['context']['exception']['file'])) { - if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) { + if (null === $message->getFile() && isset($context['exception']['file'])) { + if (preg_match("/^(.+):([0-9]+)$/", $context['exception']['file'], $matches)) { $message->setFile($matches[1]); $message->setLine($matches[2]); } diff --git a/src/Monolog/Formatter/LineFormatter.php b/src/Monolog/Formatter/LineFormatter.php index fa87cf35..f3eeb232 100644 --- a/src/Monolog/Formatter/LineFormatter.php +++ b/src/Monolog/Formatter/LineFormatter.php @@ -110,6 +110,9 @@ class LineFormatter extends NormalizerFormatter // remove leftover %extra.xxx% and %context.xxx% if any if (false !== strpos($output, '%')) { $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + if (null === $output) { + throw new \RuntimeException('Failed to run preg_replace: ' . preg_last_error() . ' / ' . preg_last_error_msg()); + } } return $output; diff --git a/src/Monolog/Formatter/LogstashFormatter.php b/src/Monolog/Formatter/LogstashFormatter.php index 563d97db..c3371591 100644 --- a/src/Monolog/Formatter/LogstashFormatter.php +++ b/src/Monolog/Formatter/LogstashFormatter.php @@ -52,7 +52,7 @@ class LogstashFormatter extends NormalizerFormatter // logstash requires a ISO 8601 format date with optional millisecond precision. parent::__construct('Y-m-d\TH:i:s.uP'); - $this->systemName = $systemName === null ? gethostname() : $systemName; + $this->systemName = $systemName === null ? (string) gethostname() : $systemName; $this->applicationName = $applicationName; $this->extraKey = $extraKey; $this->contextKey = $contextKey; diff --git a/src/Monolog/Formatter/MongoDBFormatter.php b/src/Monolog/Formatter/MongoDBFormatter.php index adf2ce1a..202d30eb 100644 --- a/src/Monolog/Formatter/MongoDBFormatter.php +++ b/src/Monolog/Formatter/MongoDBFormatter.php @@ -37,23 +37,26 @@ class MongoDBFormatter implements FormatterInterface $this->maxNestingLevel = max($maxNestingLevel, 0); $this->exceptionTraceAsString = $exceptionTraceAsString; - $this->isLegacyMongoExt = extension_loaded('mongodb') && version_compare(phpversion('mongodb'), '1.1.9', '<='); + $this->isLegacyMongoExt = extension_loaded('mongodb') && version_compare((string) phpversion('mongodb'), '1.1.9', '<='); } /** * {@inheritDoc} * - * @return scalar[] + * @return mixed[] */ public function format(array $record): array { - return $this->formatArray($record); + /** @var mixed[] $res */ + $res = $this->formatArray($record); + + return $res; } /** * {@inheritDoc} * - * @return array + * @return array */ public function formatBatch(array $records): array { @@ -152,6 +155,7 @@ class MongoDBFormatter implements FormatterInterface ? (int) $milliseconds : (string) $milliseconds; + // @phpstan-ignore-next-line return new UTCDateTime($milliseconds); } } diff --git a/src/Monolog/Formatter/NormalizerFormatter.php b/src/Monolog/Formatter/NormalizerFormatter.php index 9363b6c4..1572b4c9 100644 --- a/src/Monolog/Formatter/NormalizerFormatter.php +++ b/src/Monolog/Formatter/NormalizerFormatter.php @@ -47,6 +47,8 @@ class NormalizerFormatter implements FormatterInterface /** * {@inheritdoc} + * + * @param mixed[] $record */ public function format(array $record) { @@ -123,7 +125,7 @@ class NormalizerFormatter implements FormatterInterface /** * @param mixed $data - * @return scalar|array + * @return null|scalar|array */ protected function normalize($data, int $depth = 0) { diff --git a/src/Monolog/Formatter/ScalarFormatter.php b/src/Monolog/Formatter/ScalarFormatter.php index f209ae5d..5439ac6e 100644 --- a/src/Monolog/Formatter/ScalarFormatter.php +++ b/src/Monolog/Formatter/ScalarFormatter.php @@ -22,20 +22,21 @@ class ScalarFormatter extends NormalizerFormatter /** * {@inheritdoc} * - * @phpstan-return scalar[] $record + * @phpstan-return array $record */ public function format(array $record): array { + $result = []; foreach ($record as $key => $value) { - $record[$key] = $this->normalizeValue($value); + $result[$key] = $this->normalizeValue($value); } - return $record; + return $result; } /** - * @param mixed $value - * @return string|int|bool|null + * @param mixed $value + * @return scalar|null */ protected function normalizeValue($value) { diff --git a/src/Monolog/Formatter/WildfireFormatter.php b/src/Monolog/Formatter/WildfireFormatter.php index 5ee94d71..71c99578 100644 --- a/src/Monolog/Formatter/WildfireFormatter.php +++ b/src/Monolog/Formatter/WildfireFormatter.php @@ -69,6 +69,7 @@ class WildfireFormatter extends NormalizerFormatter unset($record['extra']['line']); } + /** @var mixed[] $record */ $record = $this->normalize($record); $message = ['message' => $record['message']]; $handleError = false; @@ -125,7 +126,7 @@ class WildfireFormatter extends NormalizerFormatter /** * {@inheritdoc} * - * @return scalar|array|object + * @return null|scalar|array|object */ protected function normalize($data, int $depth = 0) { diff --git a/src/Monolog/Handler/AbstractProcessingHandler.php b/src/Monolog/Handler/AbstractProcessingHandler.php index eaff86ac..fa17dcd8 100644 --- a/src/Monolog/Handler/AbstractProcessingHandler.php +++ b/src/Monolog/Handler/AbstractProcessingHandler.php @@ -21,6 +21,7 @@ namespace Monolog\Handler; * * @phpstan-import-type LevelName from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type Record from \Monolog\Logger * @phpstan-type FormattedRecord array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[], formatted: mixed} */ abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface @@ -38,6 +39,7 @@ abstract class AbstractProcessingHandler extends AbstractHandler implements Proc } if ($this->processors) { + /** @var Record $record */ $record = $this->processRecord($record); } diff --git a/src/Monolog/Handler/AmqpHandler.php b/src/Monolog/Handler/AmqpHandler.php index ea7f5d3f..e30d784d 100644 --- a/src/Monolog/Handler/AmqpHandler.php +++ b/src/Monolog/Handler/AmqpHandler.php @@ -94,6 +94,7 @@ class AmqpHandler extends AbstractProcessingHandler continue; } + /** @var Record $record */ $record = $this->processRecord($record); $data = $this->getFormatter()->format($record); diff --git a/src/Monolog/Handler/BrowserConsoleHandler.php b/src/Monolog/Handler/BrowserConsoleHandler.php index 4177046f..4c938426 100644 --- a/src/Monolog/Handler/BrowserConsoleHandler.php +++ b/src/Monolog/Handler/BrowserConsoleHandler.php @@ -197,7 +197,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; static $labels = []; - return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) { + $style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) { if (trim($m[1]) === 'autolabel') { // Format the string as a label with consistent auto assigned background color if (!isset($labels[$string])) { @@ -210,6 +210,12 @@ class BrowserConsoleHandler extends AbstractProcessingHandler return $m[1]; }, $style); + + if (null === $style) { + throw new \RuntimeException('Failed to run preg_replace_callback: ' . preg_last_error() . ' / ' . preg_last_error_msg()); + } + + return $style; } /** diff --git a/src/Monolog/Handler/BufferHandler.php b/src/Monolog/Handler/BufferHandler.php index e4b46a97..e8dd594a 100644 --- a/src/Monolog/Handler/BufferHandler.php +++ b/src/Monolog/Handler/BufferHandler.php @@ -80,6 +80,7 @@ class BufferHandler extends AbstractHandler implements ProcessableHandlerInterfa } if ($this->processors) { + /** @var Record $record */ $record = $this->processRecord($record); } diff --git a/src/Monolog/Handler/ChromePHPHandler.php b/src/Monolog/Handler/ChromePHPHandler.php index 27214665..125b2658 100644 --- a/src/Monolog/Handler/ChromePHPHandler.php +++ b/src/Monolog/Handler/ChromePHPHandler.php @@ -22,6 +22,8 @@ use Monolog\Utils; * This also works out of the box with Firefox 43+ * * @author Christophe Coevoet + * + * @phpstan-import-type Record from \Monolog\Logger */ class ChromePHPHandler extends AbstractProcessingHandler { @@ -87,7 +89,9 @@ class ChromePHPHandler extends AbstractProcessingHandler if ($record['level'] < $this->level) { continue; } - $messages[] = $this->processRecord($record); + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; } if (!empty($messages)) { diff --git a/src/Monolog/Handler/CubeHandler.php b/src/Monolog/Handler/CubeHandler.php index d40ac435..e95af66b 100644 --- a/src/Monolog/Handler/CubeHandler.php +++ b/src/Monolog/Handler/CubeHandler.php @@ -46,7 +46,7 @@ class CubeHandler extends AbstractProcessingHandler { $urlInfo = parse_url($url); - if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); } @@ -76,11 +76,12 @@ class CubeHandler extends AbstractProcessingHandler throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); } - $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); - if (!$this->udpConnection) { + $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (false === $udpConnection) { throw new \LogicException('Unable to create a socket'); } + $this->udpConnection = $udpConnection; if (!socket_connect($this->udpConnection, $this->host, $this->port)) { throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); } @@ -98,12 +99,12 @@ class CubeHandler extends AbstractProcessingHandler throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler'); } - $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); - - if (!$this->httpConnection) { + $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + if (false === $httpConnection) { throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); } + $this->httpConnection = $httpConnection; curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); } @@ -150,6 +151,10 @@ class CubeHandler extends AbstractProcessingHandler $this->connectHttp(); } + if (null === $this->httpConnection) { + throw new \LogicException('No connection could be established'); + } + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', diff --git a/src/Monolog/Handler/DeduplicationHandler.php b/src/Monolog/Handler/DeduplicationHandler.php index be27c03a..9b85ae7e 100644 --- a/src/Monolog/Handler/DeduplicationHandler.php +++ b/src/Monolog/Handler/DeduplicationHandler.php @@ -12,6 +12,7 @@ namespace Monolog\Handler; use Monolog\Logger; +use Psr\Log\LogLevel; /** * Simple handler wrapper that deduplicates log records across multiple requests @@ -34,6 +35,8 @@ use Monolog\Logger; * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ class DeduplicationHandler extends BufferHandler { @@ -43,7 +46,7 @@ class DeduplicationHandler extends BufferHandler protected $deduplicationStore; /** - * @var int + * @var Level */ protected $deduplicationLevel; @@ -63,6 +66,8 @@ class DeduplicationHandler extends BufferHandler * @param string|int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::* $deduplicationLevel */ public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, int $time = 60, bool $bubble = true) { diff --git a/src/Monolog/Handler/ErrorLogHandler.php b/src/Monolog/Handler/ErrorLogHandler.php index 2b8d1a88..b5f4ef1f 100644 --- a/src/Monolog/Handler/ErrorLogHandler.php +++ b/src/Monolog/Handler/ErrorLogHandler.php @@ -79,6 +79,9 @@ class ErrorLogHandler extends AbstractProcessingHandler } $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + if ($lines === false) { + throw new \RuntimeException('Failed to preg_split formatted string: '.preg_last_error().' / '.preg_last_error_msg()); + } foreach ($lines as $line) { error_log($line, $this->messageType); } diff --git a/src/Monolog/Handler/FallbackGroupHandler.php b/src/Monolog/Handler/FallbackGroupHandler.php index 9c92fa2d..0d84369c 100644 --- a/src/Monolog/Handler/FallbackGroupHandler.php +++ b/src/Monolog/Handler/FallbackGroupHandler.php @@ -13,6 +13,15 @@ namespace Monolog\Handler; use Throwable; +/** + * Forwards records to at most one handler + * + * If a handler fails, the exception is suppressed and the record is forwarded to the next handler. + * + * As soon as one handler handles a record successfully, the handling stops there. + * + * @phpstan-import-type Record from \Monolog\Logger + */ class FallbackGroupHandler extends GroupHandler { /** @@ -21,6 +30,7 @@ class FallbackGroupHandler extends GroupHandler public function handle(array $record): bool { if ($this->processors) { + /** @var Record $record */ $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { @@ -45,6 +55,7 @@ class FallbackGroupHandler extends GroupHandler foreach ($records as $record) { $processed[] = $this->processRecord($record); } + /** @var Record[] $records */ $records = $processed; } diff --git a/src/Monolog/Handler/FilterHandler.php b/src/Monolog/Handler/FilterHandler.php index cad7637d..40a5efb4 100644 --- a/src/Monolog/Handler/FilterHandler.php +++ b/src/Monolog/Handler/FilterHandler.php @@ -64,6 +64,7 @@ class FilterHandler extends Handler implements ProcessableHandlerInterface, Rese * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel */ public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, bool $bubble = true) { @@ -89,6 +90,7 @@ class FilterHandler extends Handler implements ProcessableHandlerInterface, Rese * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array * * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel */ public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY): self { @@ -124,6 +126,7 @@ class FilterHandler extends Handler implements ProcessableHandlerInterface, Rese } if ($this->processors) { + /** @var Record $record */ $record = $this->processRecord($record); } diff --git a/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php index 599d76c6..7b9abb58 100644 --- a/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php +++ b/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -12,6 +12,7 @@ namespace Monolog\Handler\FingersCrossed; use Monolog\Logger; +use Psr\Log\LogLevel; /** * Channel and Error level based monolog activation strategy. Allows to trigger activation @@ -35,11 +36,12 @@ use Monolog\Logger; * * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class ChannelLevelActivationStrategy implements ActivationStrategyInterface { /** - * @var int + * @var Level */ private $defaultActionLevel; @@ -52,7 +54,8 @@ class ChannelLevelActivationStrategy implements ActivationStrategyInterface * @param int|string $defaultActionLevel The default action level to be used if the record's category doesn't match any * @param array $channelToActionLevel An array that maps channel names to action levels. * - * @phpstan-param array $channelToActionLevel + * @phpstan-param array $channelToActionLevel + * @phpstan-param Level|LevelName|LogLevel::* $defaultActionLevel */ public function __construct($defaultActionLevel, array $channelToActionLevel = []) { diff --git a/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php index 71601e40..5ec88eab 100644 --- a/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php +++ b/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -12,21 +12,27 @@ namespace Monolog\Handler\FingersCrossed; use Monolog\Logger; +use Psr\Log\LogLevel; /** * Error level based activation strategy. * * @author Johannes M. Schmitt + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class ErrorLevelActivationStrategy implements ActivationStrategyInterface { /** - * @var int + * @var Level */ private $actionLevel; /** * @param int|string $actionLevel Level or name or value + * + * @phpstan-param Level|LevelName|LogLevel::* $actionLevel */ public function __construct($actionLevel) { diff --git a/src/Monolog/Handler/FingersCrossedHandler.php b/src/Monolog/Handler/FingersCrossedHandler.php index 45801ec7..266d4d59 100644 --- a/src/Monolog/Handler/FingersCrossedHandler.php +++ b/src/Monolog/Handler/FingersCrossedHandler.php @@ -16,6 +16,7 @@ use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; use Monolog\Logger; use Monolog\ResettableInterface; use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; /** * Buffers all records until a certain level is reached @@ -73,6 +74,9 @@ class FingersCrossedHandler extends Handler implements ProcessableHandlerInterfa * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) * @param int|string $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $passthruLevel + * @phpstan-param Level|LevelName|LogLevel::*|ActivationStrategyInterface $activationStrategy */ public function __construct($handler, $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, $passthruLevel = null) { @@ -127,6 +131,7 @@ class FingersCrossedHandler extends Handler implements ProcessableHandlerInterfa public function handle(array $record): bool { if ($this->processors) { + /** @var Record $record */ $record = $this->processRecord($record); } @@ -152,7 +157,7 @@ class FingersCrossedHandler extends Handler implements ProcessableHandlerInterfa { $this->flushBuffer(); - $this->handler->close(); + $this->getHandler()->close(); } public function reset() diff --git a/src/Monolog/Handler/FirePHPHandler.php b/src/Monolog/Handler/FirePHPHandler.php index e37180ca..ad6c5698 100644 --- a/src/Monolog/Handler/FirePHPHandler.php +++ b/src/Monolog/Handler/FirePHPHandler.php @@ -67,11 +67,15 @@ class FirePHPHandler extends AbstractProcessingHandler * @param string $message Log message * * @return array Complete header string ready for the client as key and message as value + * + * @phpstan-return non-empty-array */ protected function createHeader(array $meta, string $message): array { $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta)); + // See https://github.com/phpstan/phpstan/issues/5219 + // @phpstan-ignore-next-line return [$header => $message]; } @@ -80,6 +84,8 @@ class FirePHPHandler extends AbstractProcessingHandler * * @return array * + * @phpstan-return non-empty-array + * * @see createHeader() * * @phpstan-param FormattedRecord $record diff --git a/src/Monolog/Handler/FleepHookHandler.php b/src/Monolog/Handler/FleepHookHandler.php index 81584407..34cda19a 100644 --- a/src/Monolog/Handler/FleepHookHandler.php +++ b/src/Monolog/Handler/FleepHookHandler.php @@ -43,8 +43,6 @@ class FleepHookHandler extends SocketHandler * see https://fleep.io/integrations/webhooks/ * * @param string $token Webhook token - * @param string|int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @throws MissingExtensionException */ public function __construct(string $token, $level = Logger::DEBUG, bool $bubble = true) diff --git a/src/Monolog/Handler/FlowdockHandler.php b/src/Monolog/Handler/FlowdockHandler.php index c12fe334..d843b109 100644 --- a/src/Monolog/Handler/FlowdockHandler.php +++ b/src/Monolog/Handler/FlowdockHandler.php @@ -37,9 +37,6 @@ class FlowdockHandler extends SocketHandler protected $apiToken; /** - * @param string|int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * * @throws MissingExtensionException if OpenSSL is missing */ public function __construct(string $apiToken, $level = Logger::DEBUG, bool $bubble = true) diff --git a/src/Monolog/Handler/GelfHandler.php b/src/Monolog/Handler/GelfHandler.php index 5a4c8ac9..64d9da80 100644 --- a/src/Monolog/Handler/GelfHandler.php +++ b/src/Monolog/Handler/GelfHandler.php @@ -25,12 +25,12 @@ use Monolog\Formatter\FormatterInterface; class GelfHandler extends AbstractProcessingHandler { /** - * @var PublisherInterface|null the publisher object that sends the message to the server + * @var PublisherInterface the publisher object that sends the message to the server */ protected $publisher; /** - * @param PublisherInterface $publisher a publisher object + * @param PublisherInterface $publisher a gelf publisher object */ public function __construct(PublisherInterface $publisher, $level = Logger::DEBUG, bool $bubble = true) { diff --git a/src/Monolog/Handler/GroupHandler.php b/src/Monolog/Handler/GroupHandler.php index 7265d87a..3c9dc4b3 100644 --- a/src/Monolog/Handler/GroupHandler.php +++ b/src/Monolog/Handler/GroupHandler.php @@ -18,6 +18,8 @@ use Monolog\ResettableInterface; * Forwards records to multiple handlers * * @author Lenar Lõhmus + * + * @phpstan-import-type Record from \Monolog\Logger */ class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface { @@ -45,7 +47,7 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface, Reset } /** - * {@inheritdoc} + * {@inheritDoc} */ public function isHandling(array $record): bool { @@ -59,11 +61,12 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface, Reset } /** - * {@inheritdoc} + * {@inheritDoc} */ public function handle(array $record): bool { if ($this->processors) { + /** @var Record $record */ $record = $this->processRecord($record); } @@ -75,7 +78,7 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface, Reset } /** - * {@inheritdoc} + * {@inheritDoc} */ public function handleBatch(array $records): void { @@ -84,6 +87,7 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface, Reset foreach ($records as $record) { $processed[] = $this->processRecord($record); } + /** @var Record[] $records */ $records = $processed; } @@ -113,7 +117,7 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface, Reset } /** - * {@inheritdoc} + * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter): HandlerInterface { diff --git a/src/Monolog/Handler/InsightOpsHandler.php b/src/Monolog/Handler/InsightOpsHandler.php index 5ddc66bd..fc34e815 100644 --- a/src/Monolog/Handler/InsightOpsHandler.php +++ b/src/Monolog/Handler/InsightOpsHandler.php @@ -30,8 +30,6 @@ class InsightOpsHandler extends SocketHandler * @param string $token Log token supplied by InsightOps * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'. * @param bool $useSSL Whether or not SSL encryption should be used - * @param string|int $level The minimum logging level to trigger this handler - * @param bool $bubble Whether or not messages that are handled should bubble up the stack. * * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing */ diff --git a/src/Monolog/Handler/LogEntriesHandler.php b/src/Monolog/Handler/LogEntriesHandler.php index 66de5f80..28f38b44 100644 --- a/src/Monolog/Handler/LogEntriesHandler.php +++ b/src/Monolog/Handler/LogEntriesHandler.php @@ -26,8 +26,6 @@ class LogEntriesHandler extends SocketHandler /** * @param string $token Log token supplied by LogEntries * @param bool $useSSL Whether or not SSL encryption should be used. - * @param string|int $level The minimum logging level to trigger this handler - * @param bool $bubble Whether or not messages that are handled should bubble up the stack. * @param string $host Custom hostname to send the data to if needed * * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing diff --git a/src/Monolog/Handler/LogmaticHandler.php b/src/Monolog/Handler/LogmaticHandler.php index 209af161..7e0c4c58 100644 --- a/src/Monolog/Handler/LogmaticHandler.php +++ b/src/Monolog/Handler/LogmaticHandler.php @@ -40,8 +40,6 @@ class LogmaticHandler extends SocketHandler * @param string $hostname Host name supplied by Logmatic. * @param string $appname Application name supplied by Logmatic. * @param bool $useSSL Whether or not SSL encryption should be used. - * @param int|string $level The minimum logging level to trigger this handler. - * @param bool $bubble Whether or not messages that are handled should bubble up the stack. * * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing */ diff --git a/src/Monolog/Handler/MailHandler.php b/src/Monolog/Handler/MailHandler.php index a0148a58..2feadb17 100644 --- a/src/Monolog/Handler/MailHandler.php +++ b/src/Monolog/Handler/MailHandler.php @@ -34,7 +34,9 @@ abstract class MailHandler extends AbstractProcessingHandler if ($record['level'] < $this->level) { continue; } - $messages[] = $this->processRecord($record); + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; } if (!empty($messages)) { @@ -61,7 +63,7 @@ abstract class MailHandler extends AbstractProcessingHandler } /** - * @phpstan-param Record[] $records + * @phpstan-param non-empty-array $records * @phpstan-return Record */ protected function getHighestRecord(array $records): array diff --git a/src/Monolog/Handler/ProcessHandler.php b/src/Monolog/Handler/ProcessHandler.php index d057efae..193da724 100644 --- a/src/Monolog/Handler/ProcessHandler.php +++ b/src/Monolog/Handler/ProcessHandler.php @@ -162,7 +162,7 @@ class ProcessHandler extends AbstractProcessingHandler */ protected function readProcessErrors(): string { - return stream_get_contents($this->pipes[2]); + return (string) stream_get_contents($this->pipes[2]); } /** diff --git a/src/Monolog/Handler/PushoverHandler.php b/src/Monolog/Handler/PushoverHandler.php index 12cc101c..255af5c0 100644 --- a/src/Monolog/Handler/PushoverHandler.php +++ b/src/Monolog/Handler/PushoverHandler.php @@ -13,6 +13,7 @@ namespace Monolog\Handler; use Monolog\Logger; use Monolog\Utils; +use Psr\Log\LogLevel; /** * Sends notifications through the pushover api to mobile phones @@ -21,6 +22,8 @@ use Monolog\Utils; * @see https://www.pushover.net/api * * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class PushoverHandler extends SocketHandler { @@ -28,7 +31,7 @@ class PushoverHandler extends SocketHandler private $token; /** @var array */ private $users; - /** @var ?string */ + /** @var string */ private $title; /** @var string|int|null */ private $user = null; @@ -80,8 +83,6 @@ class PushoverHandler extends SocketHandler * @param string $token Pushover api token * @param string|array $users Pushover user id or array of ids the message will be sent to * @param string|null $title Title sent to the Pushover API - * @param string|int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not * the pushover.net app owner. OpenSSL is required for this option. * @param string|int $highPriorityLevel The minimum logging level at which this handler will start @@ -93,7 +94,9 @@ class PushoverHandler extends SocketHandler * @param int $expire The expire parameter specifies how many seconds your notification will continue * to be retried for (every retry seconds). * - * @phpstan-param string|array $users + * @phpstan-param string|array $users + * @phpstan-param Level|LevelName|LogLevel::* $highPriorityLevel + * @phpstan-param Level|LevelName|LogLevel::* $emergencyLevel */ public function __construct( string $token, @@ -112,7 +115,7 @@ class PushoverHandler extends SocketHandler $this->token = $token; $this->users = (array) $users; - $this->title = $title ?: gethostname(); + $this->title = $title ?: (string) gethostname(); $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); $this->retry = $retry; @@ -195,6 +198,8 @@ class PushoverHandler extends SocketHandler /** * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value */ public function setHighPriorityLevel($value): self { @@ -205,6 +210,8 @@ class PushoverHandler extends SocketHandler /** * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value */ public function setEmergencyLevel($value): self { diff --git a/src/Monolog/Handler/RollbarHandler.php b/src/Monolog/Handler/RollbarHandler.php index 2d94da42..aaec1262 100644 --- a/src/Monolog/Handler/RollbarHandler.php +++ b/src/Monolog/Handler/RollbarHandler.php @@ -97,6 +97,7 @@ class RollbarHandler extends AbstractProcessingHandler $toLog = $record['message']; } + // @phpstan-ignore-next-line $this->rollbarLogger->log($context['level'], $toLog, $context); $this->hasRecords = true; diff --git a/src/Monolog/Handler/RotatingFileHandler.php b/src/Monolog/Handler/RotatingFileHandler.php index 949fd1b8..dbb008d2 100644 --- a/src/Monolog/Handler/RotatingFileHandler.php +++ b/src/Monolog/Handler/RotatingFileHandler.php @@ -46,8 +46,6 @@ class RotatingFileHandler extends StreamHandler /** * @param string $filename * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) - * @param string|int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) * @param bool $useLocking Try to lock log file before doing any writes */ @@ -116,7 +114,7 @@ class RotatingFileHandler extends StreamHandler { // on the first record written, if the log is new, we should rotate (once per day) if (null === $this->mustRotate) { - $this->mustRotate = !file_exists($this->url); + $this->mustRotate = null === $this->url || !file_exists($this->url); } if ($this->nextRotation <= $record['datetime']) { @@ -142,6 +140,11 @@ class RotatingFileHandler extends StreamHandler } $logFiles = glob($this->getGlobPattern()); + if (false === $logFiles) { + // failed to glob + return; + } + if ($this->maxFiles >= count($logFiles)) { // no files to remove return; @@ -176,7 +179,7 @@ class RotatingFileHandler extends StreamHandler $fileInfo['dirname'] . '/' . $this->filenameFormat ); - if (!empty($fileInfo['extension'])) { + if (isset($fileInfo['extension'])) { $timedFilename .= '.'.$fileInfo['extension']; } @@ -191,7 +194,7 @@ class RotatingFileHandler extends StreamHandler [$fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'], $fileInfo['dirname'] . '/' . $this->filenameFormat ); - if (!empty($fileInfo['extension'])) { + if (isset($fileInfo['extension'])) { $glob .= '.'.$fileInfo['extension']; } diff --git a/src/Monolog/Handler/SamplingHandler.php b/src/Monolog/Handler/SamplingHandler.php index d76f1d35..a7ba8639 100644 --- a/src/Monolog/Handler/SamplingHandler.php +++ b/src/Monolog/Handler/SamplingHandler.php @@ -36,7 +36,7 @@ class SamplingHandler extends AbstractHandler implements ProcessableHandlerInter /** * @var HandlerInterface|callable - * @phpstan-var HandlerInterface|callable(Record, HandlerInterface): HandlerInterface + * @phpstan-var HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface */ protected $handler; @@ -46,7 +46,7 @@ class SamplingHandler extends AbstractHandler implements ProcessableHandlerInter protected $factor; /** - * @psalm-param HandlerInterface|callable(Record, HandlerInterface): HandlerInterface $handler + * @psalm-param HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface $handler * * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $samplingHandler). * @param int $factor Sample factor (e.g. 10 means every ~10th record is sampled) @@ -71,6 +71,7 @@ class SamplingHandler extends AbstractHandler implements ProcessableHandlerInter { if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { if ($this->processors) { + /** @var Record $record */ $record = $this->processRecord($record); } diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index e2c849c1..13c3a102 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -25,6 +25,7 @@ use Monolog\Formatter\FormatterInterface; * @see https://api.slack.com/docs/message-attachments * * @phpstan-import-type FormattedRecord from \Monolog\Handler\AbstractProcessingHandler + * @phpstan-import-type Record from \Monolog\Logger */ class SlackRecord { @@ -121,7 +122,7 @@ class SlackRecord * is expecting. * * @phpstan-param FormattedRecord $record - * @phpstan-return string[] + * @phpstan-return mixed[] */ public function getSlackData(array $record): array { @@ -137,6 +138,7 @@ class SlackRecord } if ($this->formatter && !$this->useAttachment) { + /** @phpstan-ignore-next-line */ $message = $this->formatter->format($record); } else { $message = $record['message']; @@ -221,6 +223,7 @@ class SlackRecord */ public function stringify(array $fields): string { + /** @var Record $fields */ $normalized = $this->normalizerFormatter->format($fields); $hasSecondDimension = count(array_filter($normalized, 'is_array')); @@ -341,8 +344,11 @@ class SlackRecord */ private function generateAttachmentFields(array $data): array { + /** @var Record $data */ + $normalized = $this->normalizerFormatter->format($data); + $fields = array(); - foreach ($this->normalizerFormatter->format($data) as $key => $value) { + foreach ($normalized as $key => $value) { $fields[] = $this->generateAttachmentField((string) $key, $value); } diff --git a/src/Monolog/Handler/SocketHandler.php b/src/Monolog/Handler/SocketHandler.php index e35cac92..c98249e8 100644 --- a/src/Monolog/Handler/SocketHandler.php +++ b/src/Monolog/Handler/SocketHandler.php @@ -216,7 +216,7 @@ class SocketHandler extends AbstractProcessingHandler /** * Wrapper to allow mocking * - * @return resource|bool + * @return resource|false */ protected function pfsockopen() { @@ -226,7 +226,7 @@ class SocketHandler extends AbstractProcessingHandler /** * Wrapper to allow mocking * - * @return resource|bool + * @return resource|false */ protected function fsockopen() { @@ -245,6 +245,10 @@ class SocketHandler extends AbstractProcessingHandler $seconds = floor($this->timeout); $microseconds = round(($this->timeout - $seconds) * 1e6); + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetTimeout called but $this->resource is not a resource'); + } + return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds); } @@ -257,6 +261,10 @@ class SocketHandler extends AbstractProcessingHandler */ protected function streamSetChunkSize() { + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetChunkSize called but $this->resource is not a resource'); + } + return stream_set_chunk_size($this->resource, $this->chunkSize); } @@ -267,6 +275,10 @@ class SocketHandler extends AbstractProcessingHandler */ protected function fwrite(string $data) { + if (!is_resource($this->resource)) { + throw new \LogicException('fwrite called but $this->resource is not a resource'); + } + return @fwrite($this->resource, $data); } @@ -277,6 +289,10 @@ class SocketHandler extends AbstractProcessingHandler */ protected function streamGetMetadata() { + if (!is_resource($this->resource)) { + throw new \LogicException('streamGetMetadata called but $this->resource is not a resource'); + } + return stream_get_meta_data($this->resource); } @@ -325,7 +341,7 @@ class SocketHandler extends AbstractProcessingHandler } else { $resource = $this->fsockopen(); } - if (!$resource) { + if (is_bool($resource)) { throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); } $this->resource = $resource; @@ -361,7 +377,7 @@ class SocketHandler extends AbstractProcessingHandler } $sent += $chunk; $socketInfo = $this->streamGetMetadata(); - if ($socketInfo['timed_out']) { + if (is_array($socketInfo) && $socketInfo['timed_out']) { throw new \RuntimeException("Write timed-out"); } diff --git a/src/Monolog/Handler/StreamHandler.php b/src/Monolog/Handler/StreamHandler.php index 51eb0268..0f46f631 100644 --- a/src/Monolog/Handler/StreamHandler.php +++ b/src/Monolog/Handler/StreamHandler.php @@ -101,34 +101,41 @@ class StreamHandler extends AbstractProcessingHandler protected function write(array $record): void { if (!is_resource($this->stream)) { - if (null === $this->url || '' === $this->url) { + $url = $this->url; + if (null === $url || '' === $url) { throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); } - $this->createDir(); + $this->createDir($url); $this->errorMessage = null; set_error_handler([$this, 'customErrorHandler']); - $this->stream = fopen($this->url, 'a'); + $stream = fopen($url, 'a'); if ($this->filePermission !== null) { - @chmod($this->url, $this->filePermission); + @chmod($url, $this->filePermission); } restore_error_handler(); - if (!is_resource($this->stream)) { + if (!is_resource($stream)) { $this->stream = null; - throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $this->url)); + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $url)); } - stream_set_chunk_size($this->stream, self::MAX_CHUNK_SIZE); + stream_set_chunk_size($stream, self::MAX_CHUNK_SIZE); + $this->stream = $stream; + } + + $stream = $this->stream; + if (!is_resource($stream)) { + throw new \LogicException('No stream was opened yet'); } if ($this->useLocking) { // ignoring errors here, there's not much we can do about them - flock($this->stream, LOCK_EX); + flock($stream, LOCK_EX); } - $this->streamWrite($this->stream, $record); + $this->streamWrite($stream, $record); if ($this->useLocking) { - flock($this->stream, LOCK_UN); + flock($stream, LOCK_UN); } } @@ -165,14 +172,14 @@ class StreamHandler extends AbstractProcessingHandler return null; } - private function createDir(): void + private function createDir(string $url): void { // Do not try to create dir if it has already been tried. if ($this->dirCreated) { return; } - $dir = $this->getDirFromStream($this->url); + $dir = $this->getDirFromStream($url); if (null !== $dir && !is_dir($dir)) { $this->errorMessage = null; set_error_handler([$this, 'customErrorHandler']); diff --git a/src/Monolog/Handler/SyslogHandler.php b/src/Monolog/Handler/SyslogHandler.php index 12636e53..c857d62d 100644 --- a/src/Monolog/Handler/SyslogHandler.php +++ b/src/Monolog/Handler/SyslogHandler.php @@ -36,8 +36,6 @@ class SyslogHandler extends AbstractSyslogHandler /** * @param string $ident * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant - * @param string|int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID */ public function __construct(string $ident, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, int $logopts = LOG_PID) diff --git a/src/Monolog/Handler/SyslogUdpHandler.php b/src/Monolog/Handler/SyslogUdpHandler.php index e6b7f77b..ec404acf 100644 --- a/src/Monolog/Handler/SyslogUdpHandler.php +++ b/src/Monolog/Handler/SyslogUdpHandler.php @@ -45,7 +45,6 @@ class SyslogUdpHandler extends AbstractSyslogHandler * @param string $host Either IP/hostname or a path to a unix socket (port must be 0 then) * @param int $port Port number, or 0 if $host is a unix socket * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant - * @param string|int $level The minimum logging level at which this handler will be triggered * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @param string $ident Program name or tag for each log message. * @param int $rfc RFC to format the message for. @@ -88,7 +87,12 @@ class SyslogUdpHandler extends AbstractSyslogHandler $message = implode("\n", $message); } - return preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); + $lines = preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); + if (false === $lines) { + throw new \RuntimeException('Could not preg_split: '.preg_last_error().' / '.preg_last_error_msg()); + } + + return $lines; } /** diff --git a/src/Monolog/Handler/TelegramBotHandler.php b/src/Monolog/Handler/TelegramBotHandler.php index 51fb7396..db608ab8 100644 --- a/src/Monolog/Handler/TelegramBotHandler.php +++ b/src/Monolog/Handler/TelegramBotHandler.php @@ -27,6 +27,8 @@ use Monolog\Logger; * @link https://core.telegram.org/bots/api * * @author Mazur Alexandr + * + * @phpstan-import-type Record from \Monolog\Logger */ class TelegramBotHandler extends AbstractProcessingHandler { @@ -127,6 +129,7 @@ class TelegramBotHandler extends AbstractProcessingHandler */ public function handleBatch(array $records): void { + /** @var Record[] $messages */ $messages = []; foreach ($records as $record) { @@ -135,6 +138,7 @@ class TelegramBotHandler extends AbstractProcessingHandler } if ($this->processors) { + /** @var Record $record */ $record = $this->processRecord($record); } @@ -174,6 +178,9 @@ class TelegramBotHandler extends AbstractProcessingHandler ])); $result = Curl\Util::execute($ch); + if (!is_string($result)) { + throw new RuntimeException('Telegram API error. Description: No response'); + } $result = json_decode($result, true); if ($result['ok'] === false) { diff --git a/src/Monolog/Handler/TestHandler.php b/src/Monolog/Handler/TestHandler.php index e2e3905f..384f6e90 100644 --- a/src/Monolog/Handler/TestHandler.php +++ b/src/Monolog/Handler/TestHandler.php @@ -118,6 +118,8 @@ class TestHandler extends AbstractProcessingHandler /** * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ public function hasRecords($level): bool { @@ -151,6 +153,8 @@ class TestHandler extends AbstractProcessingHandler /** * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ public function hasRecordThatContains(string $message, $level): bool { @@ -214,10 +218,11 @@ class TestHandler extends AbstractProcessingHandler if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; $level = constant('Monolog\Logger::' . strtoupper($matches[2])); - if (method_exists($this, $genericMethod)) { + $callback = [$this, $genericMethod]; + if (is_callable($callback)) { $args[] = $level; - return call_user_func_array([$this, $genericMethod], $args); + return call_user_func_array($callback, $args); } } diff --git a/src/Monolog/Handler/WhatFailureGroupHandler.php b/src/Monolog/Handler/WhatFailureGroupHandler.php index 16c3cbbb..2dd13672 100644 --- a/src/Monolog/Handler/WhatFailureGroupHandler.php +++ b/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -16,15 +16,18 @@ namespace Monolog\Handler; * and continuing through to give every handler a chance to succeed. * * @author Craig D'Amelio + * + * @phpstan-import-type Record from \Monolog\Logger */ class WhatFailureGroupHandler extends GroupHandler { /** - * {@inheritdoc} + * {@inheritDoc} */ public function handle(array $record): bool { if ($this->processors) { + /** @var Record $record */ $record = $this->processRecord($record); } @@ -40,7 +43,7 @@ class WhatFailureGroupHandler extends GroupHandler } /** - * {@inheritdoc} + * {@inheritDoc} */ public function handleBatch(array $records): void { @@ -49,6 +52,7 @@ class WhatFailureGroupHandler extends GroupHandler foreach ($records as $record) { $processed[] = $this->processRecord($record); } + /** @var Record[] $records */ $records = $processed; } diff --git a/src/Monolog/Processor/GitProcessor.php b/src/Monolog/Processor/GitProcessor.php index 337ef226..8166bdca 100644 --- a/src/Monolog/Processor/GitProcessor.php +++ b/src/Monolog/Processor/GitProcessor.php @@ -12,12 +12,16 @@ namespace Monolog\Processor; use Monolog\Logger; +use Psr\Log\LogLevel; /** * Injects Git branch and Git commit SHA in all records * * @author Nick Otter * @author Jordi Boggiano + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class GitProcessor implements ProcessorInterface { @@ -28,6 +32,8 @@ class GitProcessor implements ProcessorInterface /** * @param string|int $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ public function __construct($level = Logger::DEBUG) { diff --git a/src/Monolog/Processor/IntrospectionProcessor.php b/src/Monolog/Processor/IntrospectionProcessor.php index 5a3e93c9..0823501b 100644 --- a/src/Monolog/Processor/IntrospectionProcessor.php +++ b/src/Monolog/Processor/IntrospectionProcessor.php @@ -12,6 +12,7 @@ namespace Monolog\Processor; use Monolog\Logger; +use Psr\Log\LogLevel; /** * Injects line/file:class/function where the log message came from @@ -23,6 +24,9 @@ use Monolog\Logger; * triggered the FingersCrossedHandler. * * @author Jordi Boggiano + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class IntrospectionProcessor implements ProcessorInterface { @@ -41,6 +45,8 @@ class IntrospectionProcessor implements ProcessorInterface /** * @param string|int $level The minimum logging level at which this Processor will be triggered * @param string[] $skipClassesPartials + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ public function __construct($level = Logger::DEBUG, array $skipClassesPartials = [], int $skipStackFramesCount = 0) { diff --git a/src/Monolog/Utils.php b/src/Monolog/Utils.php index 79868198..b2d0ad58 100644 --- a/src/Monolog/Utils.php +++ b/src/Monolog/Utils.php @@ -138,6 +138,8 @@ final class Utils * @param int $code return code of json_last_error function * @param mixed $data data that was meant to be encoded * @throws \RuntimeException + * + * @return never */ private static function throwEncodeError(int $code, $data): void { @@ -186,6 +188,9 @@ final class Utils }, $data ); + if (!is_string($data)) { + throw new \RuntimeException('Failed to preg_replace_callback: '.preg_last_error().' / '.preg_last_error_msg()); + } $data = str_replace( ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'],