Explorar o código

Bump phpstan to level 8

Jordi Boggiano %!s(int64=4) %!d(string=hai) anos
pai
achega
8b5278d8e1
Modificáronse 47 ficheiros con 262 adicións e 101 borrados
  1. 11 2
      phpstan.neon.dist
  2. 2 0
      src/Monolog/Formatter/ElasticaFormatter.php
  3. 23 16
      src/Monolog/Formatter/GelfMessageFormatter.php
  4. 3 0
      src/Monolog/Formatter/LineFormatter.php
  5. 1 1
      src/Monolog/Formatter/LogstashFormatter.php
  6. 8 4
      src/Monolog/Formatter/MongoDBFormatter.php
  7. 3 1
      src/Monolog/Formatter/NormalizerFormatter.php
  8. 6 5
      src/Monolog/Formatter/ScalarFormatter.php
  9. 2 1
      src/Monolog/Formatter/WildfireFormatter.php
  10. 2 0
      src/Monolog/Handler/AbstractProcessingHandler.php
  11. 1 0
      src/Monolog/Handler/AmqpHandler.php
  12. 7 1
      src/Monolog/Handler/BrowserConsoleHandler.php
  13. 1 0
      src/Monolog/Handler/BufferHandler.php
  14. 5 1
      src/Monolog/Handler/ChromePHPHandler.php
  15. 11 6
      src/Monolog/Handler/CubeHandler.php
  16. 6 1
      src/Monolog/Handler/DeduplicationHandler.php
  17. 3 0
      src/Monolog/Handler/ErrorLogHandler.php
  18. 11 0
      src/Monolog/Handler/FallbackGroupHandler.php
  19. 3 0
      src/Monolog/Handler/FilterHandler.php
  20. 5 2
      src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php
  21. 7 1
      src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php
  22. 6 1
      src/Monolog/Handler/FingersCrossedHandler.php
  23. 6 0
      src/Monolog/Handler/FirePHPHandler.php
  24. 0 2
      src/Monolog/Handler/FleepHookHandler.php
  25. 0 3
      src/Monolog/Handler/FlowdockHandler.php
  26. 2 2
      src/Monolog/Handler/GelfHandler.php
  27. 8 4
      src/Monolog/Handler/GroupHandler.php
  28. 0 2
      src/Monolog/Handler/InsightOpsHandler.php
  29. 0 2
      src/Monolog/Handler/LogEntriesHandler.php
  30. 0 2
      src/Monolog/Handler/LogmaticHandler.php
  31. 4 2
      src/Monolog/Handler/MailHandler.php
  32. 1 1
      src/Monolog/Handler/ProcessHandler.php
  33. 12 5
      src/Monolog/Handler/PushoverHandler.php
  34. 1 0
      src/Monolog/Handler/RollbarHandler.php
  35. 8 5
      src/Monolog/Handler/RotatingFileHandler.php
  36. 3 2
      src/Monolog/Handler/SamplingHandler.php
  37. 8 2
      src/Monolog/Handler/Slack/SlackRecord.php
  38. 20 4
      src/Monolog/Handler/SocketHandler.php
  39. 19 12
      src/Monolog/Handler/StreamHandler.php
  40. 0 2
      src/Monolog/Handler/SyslogHandler.php
  41. 6 2
      src/Monolog/Handler/SyslogUdpHandler.php
  42. 7 0
      src/Monolog/Handler/TelegramBotHandler.php
  43. 7 2
      src/Monolog/Handler/TestHandler.php
  44. 6 2
      src/Monolog/Handler/WhatFailureGroupHandler.php
  45. 6 0
      src/Monolog/Processor/GitProcessor.php
  46. 6 0
      src/Monolog/Processor/IntrospectionProcessor.php
  47. 5 0
      src/Monolog/Utils.php

+ 11 - 2
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.#'

+ 2 - 0
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);

+ 23 - 16
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 <mlehner@gmail.com>
+ *
+ * @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<int, int>
+     *
+     * @phpstan-var array<Level, int>
      */
     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]);
             }

+ 3 - 0
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;

+ 1 - 1
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;

+ 8 - 4
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<scalar[]>
+     * @return array<mixed[]>
      */
     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);
     }
 }

+ 3 - 1
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<scalar>
+     * @return null|scalar|array<array|scalar|null>
      */
     protected function normalize($data, int $depth = 0)
     {

+ 6 - 5
src/Monolog/Formatter/ScalarFormatter.php

@@ -22,20 +22,21 @@ class ScalarFormatter extends NormalizerFormatter
     /**
      * {@inheritdoc}
      *
-     * @phpstan-return scalar[] $record
+     * @phpstan-return array<string, scalar|null> $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)
     {

+ 2 - 1
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<scalar>|object
+     * @return null|scalar|array<array|scalar|null>|object
      */
     protected function normalize($data, int $depth = 0)
     {

+ 2 - 0
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);
         }
 

+ 1 - 0
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);
 

+ 7 - 1
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;
     }
 
     /**

+ 1 - 0
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);
         }
 

+ 5 - 1
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 <stof@notk.org>
+ *
+ * @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)) {

+ 11 - 6
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',

+ 6 - 1
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 <j.boggiano@seld.be>
  *
  * @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)
     {

+ 3 - 0
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);
         }

+ 11 - 0
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;
         }
 

+ 3 - 0
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<Level|LevelName|LogLevel::*> $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<Level|LevelName|LogLevel::*> $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);
         }
 

+ 5 - 2
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<string, int> $channelToActionLevel An array that maps channel names to action levels.
      *
-     * @phpstan-param array<string, Level> $channelToActionLevel
+     * @phpstan-param array<string, Level>        $channelToActionLevel
+     * @phpstan-param Level|LevelName|LogLevel::* $defaultActionLevel
      */
     public function __construct($defaultActionLevel, array $channelToActionLevel = [])
     {

+ 7 - 1
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 <schmittjoh@gmail.com>
+ *
+ * @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)
     {

+ 6 - 1
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()

+ 6 - 0
src/Monolog/Handler/FirePHPHandler.php

@@ -67,11 +67,15 @@ class FirePHPHandler extends AbstractProcessingHandler
      * @param string            $message Log message
      *
      * @return array<string, string> Complete header string ready for the client as key and message as value
+     *
+     * @phpstan-return non-empty-array<string, string>
      */
     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<string, string>
      *
+     * @phpstan-return non-empty-array<string, string>
+     *
      * @see createHeader()
      *
      * @phpstan-param FormattedRecord $record

+ 0 - 2
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)

+ 0 - 3
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)

+ 2 - 2
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)
     {

+ 8 - 4
src/Monolog/Handler/GroupHandler.php

@@ -18,6 +18,8 @@ use Monolog\ResettableInterface;
  * Forwards records to multiple handlers
  *
  * @author Lenar Lõhmus <lenar@city.ee>
+ *
+ * @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
     {

+ 0 - 2
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
      */

+ 0 - 2
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

+ 0 - 2
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
      */

+ 4 - 2
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<Record> $records
      * @phpstan-return Record
      */
     protected function getHighestRecord(array $records): array

+ 1 - 1
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]);
     }
 
     /**

+ 12 - 5
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<int|string> */
     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<int|string> $users
+     * @phpstan-param string|array<int|string>    $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
     {

+ 1 - 0
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;

+ 8 - 5
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'];
         }
 

+ 3 - 2
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);
             }
 

+ 8 - 2
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);
         }
 

+ 20 - 4
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");
             }
 

+ 19 - 12
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']);

+ 0 - 2
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)

+ 6 - 2
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;
     }
 
     /**

+ 7 - 0
src/Monolog/Handler/TelegramBotHandler.php

@@ -27,6 +27,8 @@ use Monolog\Logger;
  * @link https://core.telegram.org/bots/api
  *
  * @author Mazur Alexandr <alexandrmazur96@gmail.com>
+ *
+ * @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) {

+ 7 - 2
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);
             }
         }
 

+ 6 - 2
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 <craig@damelio.ca>
+ *
+ * @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;
         }
 

+ 6 - 0
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 <j.boggiano@seld.be>
+ *
+ * @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)
     {

+ 6 - 0
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 <j.boggiano@seld.be>
+ *
+ * @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)
     {

+ 5 - 0
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(
                 ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'],
                 ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'],