Aggiornato Composer

This commit is contained in:
Paolo A
2024-05-17 12:24:19 +00:00
parent 4ac62108b5
commit ec201d75b2
2238 changed files with 38684 additions and 59785 deletions

View File

@@ -1,7 +1,63 @@
### 2.9.3 (2024-04-12)
* Fixed PHP 8.4 deprecation warnings (#1874)
### 2.9.2 (2023-10-27)
* Fixed display_errors parsing in ErrorHandler which did not support string values (#1804)
* Fixed bug where the previous error handler would not be restored in some cases where StreamHandler fails (#1815)
* Fixed normalization error when normalizing incomplete classes (#1833)
### 2.9.1 (2023-02-06)
* Fixed Logger not being serializable anymore (#1792)
### 2.9.0 (2023-02-05)
* Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748)
* Added support for enum context values in PsrLogMessageProcessor (#1773)
* Added graylog2/gelf-php 2.x support (#1747)
* Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739)
* Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791)
* Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758)
* Fixed infinite loop detection within Fibers (#1753)
* Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781)
### 2.8.0 (2022-07-24)
* Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734)
* Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723)
* Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733)
* Added `GoogleCloudLoggingFormatter` (#1719)
* Added support for Predis 2.x (#1732)
* Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724)
* Fixed serialization/unserialization of handlers to make sure private properties are included (#1727)
* Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\n` or `\r` sequences (#1720)
* Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726)
* Fixed PHP 8.2 deprecation warnings (#1722)
* Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678)
### 2.7.0 (2022-06-09)
* Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682)
* Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681)
* Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670)
* Marked the reusable `Monolog\Test\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677)
* Fixed RotatingFileHandler issue when the date format contained slashes (#1671)
### 2.6.0 (2022-05-10)
* Deprecated `SwiftMailerHandler`, use `SymfonyMailerHandler` instead
* Added `SymfonyMailerHandler` (#1663)
* Added ElasticSearch 8.x support to the ElasticsearchHandler (#1662)
* Added a way to filter/modify stack traces in LineFormatter (#1665)
* Fixed UdpSocket not being able to reopen/reconnect after close()
* Fixed infinite loops if a Handler is triggering logging while handling log records
### 2.5.0 (2022-04-08)
* Added `callType` to IntrospectionProcessor (#1612)
* Fixed AsMonologProcessor syntax to be compatible with PHP 7.2 (#1651)
* Added `callType` to IntrospectionProcessor (#1612)
* Fixed AsMonologProcessor syntax to be compatible with PHP 7.2 (#1651)
### 2.4.0 (2022-03-14)

View File

@@ -17,20 +17,24 @@
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
"ext-json": "*",
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7",
"elasticsearch/elasticsearch": "^7 || ^8",
"graylog2/gelf-php": "^1.4.2 || ^2@dev",
"guzzlehttp/guzzle": "^7.4",
"guzzlehttp/psr7": "^2.2",
"mongodb/mongodb": "^1.8",
"graylog2/gelf-php": "^1.4.2",
"php-amqplib/php-amqplib": "~2.4 || ^3",
"php-console/php-console": "^3.1.3",
"phpspec/prophecy": "^1.6.1",
"phpunit/phpunit": "^8.5",
"predis/predis": "^1.1",
"phpspec/prophecy": "^1.15",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^8.5.38 || ^9.6.19",
"predis/predis": "^1.1 || ^2.0",
"rollbar/rollbar": "^1.3 || ^2 || ^3",
"ruflin/elastica": ">=0.90@dev",
"ruflin/elastica": "^7",
"swiftmailer/swiftmailer": "^5.3|^6.0",
"phpstan/phpstan": "^0.12.91"
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
"suggest": {
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
@@ -43,7 +47,6 @@
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"php-console/php-console": "Allow sending log messages to Google Chrome",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
@@ -70,6 +73,9 @@
"config": {
"lock": false,
"sort-packages": true,
"platform-check": false
"platform-check": false,
"allow-plugins": {
"composer/package-versions-deprecated": true
}
}
}

View File

@@ -30,6 +30,8 @@ class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable
{
$this->useMicroseconds = $useMicroseconds;
// if you like to use a custom time to pass to Logger::addRecord directly,
// call modify() or setTimestamp() on this instance to change the date after creating it
parent::__construct('now', $timezone);
}

View File

@@ -46,8 +46,8 @@ class ErrorHandler
private $fatalLevel = LogLevel::ALERT;
/** @var ?string */
private $reservedMemory = null;
/** @var ?mixed */
private $lastFatalTrace;
/** @var ?array{type: int, message: string, file: string, line: int, trace: mixed} */
private $lastFatalData = null;
/** @var int[] */
private static $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
@@ -198,7 +198,7 @@ class ErrorHandler
($this->previousExceptionHandler)($e);
}
if (!headers_sent() && !ini_get('display_errors')) {
if (!headers_sent() && in_array(strtolower((string) ini_get('display_errors')), ['0', '', 'false', 'off', 'none', 'no'], true)) {
http_response_code(500);
}
@@ -223,7 +223,7 @@ class ErrorHandler
} else {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
array_shift($trace); // Exclude handleError from trace
$this->lastFatalTrace = $trace;
$this->lastFatalData = ['type' => $code, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $trace];
}
if ($this->previousErrorHandler === true) {
@@ -242,12 +242,18 @@ class ErrorHandler
{
$this->reservedMemory = '';
$lastError = error_get_last();
if (is_array($this->lastFatalData)) {
$lastError = $this->lastFatalData;
} else {
$lastError = error_get_last();
}
if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) {
$trace = $lastError['trace'] ?? null;
$this->logger->log(
$this->fatalLevel,
'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace]
['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $trace]
);
if ($this->logger instanceof Logger) {

View File

@@ -15,6 +15,7 @@ namespace Monolog\Formatter;
* formats the record to be used in the FlowdockHandler
*
* @author Dominik Liebler <liebler.dominik@gmail.com>
* @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4
*/
class FlowdockFormatter implements FormatterInterface
{

View File

@@ -47,6 +47,11 @@ class GelfMessageFormatter extends NormalizerFormatter
*/
protected $maxLength;
/**
* @var int
*/
private $gelfVersion = 2;
/**
* Translates Monolog log levels to Graylog2 log priorities.
*
@@ -78,6 +83,10 @@ class GelfMessageFormatter extends NormalizerFormatter
$this->extraPrefix = is_null($extraPrefix) ? '' : $extraPrefix;
$this->contextPrefix = $contextPrefix;
$this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength;
if (method_exists(Message::class, 'setFacility')) {
$this->gelfVersion = 1;
}
}
/**
@@ -113,16 +122,20 @@ class GelfMessageFormatter extends NormalizerFormatter
$message->setShortMessage(Utils::substr($record['message'], 0, $this->maxLength));
}
if (isset($record['channel'])) {
$message->setFacility($record['channel']);
}
if (isset($extra['line'])) {
$message->setLine($extra['line']);
unset($extra['line']);
}
if (isset($extra['file'])) {
$message->setFile($extra['file']);
unset($extra['file']);
if ($this->gelfVersion === 1) {
if (isset($record['channel'])) {
$message->setFacility($record['channel']);
}
if (isset($extra['line'])) {
$message->setLine($extra['line']);
unset($extra['line']);
}
if (isset($extra['file'])) {
$message->setFile($extra['file']);
unset($extra['file']);
}
} else {
$message->setAdditional('facility', $record['channel']);
}
foreach ($extra as $key => $val) {
@@ -147,11 +160,13 @@ class GelfMessageFormatter extends NormalizerFormatter
$message->setAdditional($this->contextPrefix . $key, $val);
}
/** @phpstan-ignore-next-line */
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]);
if ($this->gelfVersion === 1) {
/** @phpstan-ignore-next-line */
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]);
}
}
}

View File

@@ -178,12 +178,25 @@ class JsonFormatter extends NormalizerFormatter
return $normalized;
}
if ($data instanceof \DateTimeInterface) {
return $this->formatDate($data);
}
if (is_object($data)) {
if ($data instanceof \DateTimeInterface) {
return $this->formatDate($data);
}
if ($data instanceof Throwable) {
return $this->normalizeException($data, $depth);
if ($data instanceof Throwable) {
return $this->normalizeException($data, $depth);
}
// if the object has specific json serializability we want to make sure we skip the __toString treatment below
if ($data instanceof \JsonSerializable) {
return $data;
}
if (method_exists($data, '__toString')) {
return $data->__toString();
}
return $data;
}
if (is_resource($data)) {

View File

@@ -33,6 +33,8 @@ class LineFormatter extends NormalizerFormatter
protected $ignoreEmptyContextAndExtra;
/** @var bool */
protected $includeStacktraces;
/** @var ?callable */
protected $stacktracesParser;
/**
* @param string|null $format The format of the message
@@ -49,11 +51,12 @@ class LineFormatter extends NormalizerFormatter
parent::__construct($dateFormat);
}
public function includeStacktraces(bool $include = true): self
public function includeStacktraces(bool $include = true, ?callable $parser = null): self
{
$this->includeStacktraces = $include;
if ($this->includeStacktraces) {
$this->allowInlineLineBreaks = true;
$this->stacktracesParser = $parser;
}
return $this;
@@ -150,6 +153,12 @@ class LineFormatter extends NormalizerFormatter
if ($previous = $e->getPrevious()) {
do {
$depth++;
if ($depth > $this->maxNormalizeDepth) {
$str .= "\n[previous exception] Over " . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
break;
}
$str .= "\n[previous exception] " . $this->formatException($previous);
} while ($previous = $previous->getPrevious());
}
@@ -177,7 +186,11 @@ class LineFormatter extends NormalizerFormatter
{
if ($this->allowInlineLineBreaks) {
if (0 === strpos($str, '{')) {
return str_replace(array('\r', '\n'), array("\r", "\n"), $str);
$str = preg_replace('/(?<!\\\\)\\\\[rn]/', "\n", $str);
if (null === $str) {
$pcreErrorCode = preg_last_error();
throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
}
}
return $str;
@@ -209,9 +222,25 @@ class LineFormatter extends NormalizerFormatter
$str .= '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')';
if ($this->includeStacktraces) {
$str .= "\n[stacktrace]\n" . $e->getTraceAsString() . "\n";
$str .= $this->stacktracesParser($e);
}
return $str;
}
private function stacktracesParser(\Throwable $e): string
{
$trace = $e->getTraceAsString();
if ($this->stacktracesParser) {
$trace = $this->stacktracesParserCustom($trace);
}
return "\n[stacktrace]\n" . $trace . "\n";
}
private function stacktracesParserCustom(string $trace): string
{
return implode("\n", array_filter(array_map($this->stacktracesParser, explode("\n", $trace))));
}
}

View File

@@ -174,6 +174,9 @@ class NormalizerFormatter implements FormatterInterface
if ($data instanceof \JsonSerializable) {
/** @var null|scalar|array<array|scalar|null> $value */
$value = $data->jsonSerialize();
} elseif (\get_class($data) === '__PHP_Incomplete_Class') {
$accessor = new \ArrayObject($data);
$value = (string) $accessor['__PHP_Incomplete_Class_Name'];
} elseif (method_exists($data, '__toString')) {
/** @var string $value */
$value = $data->__toString();
@@ -198,6 +201,10 @@ class NormalizerFormatter implements FormatterInterface
*/
protected function normalizeException(Throwable $e, int $depth = 0)
{
if ($depth > $this->maxNormalizeDepth) {
return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'];
}
if ($e instanceof \JsonSerializable) {
return (array) $e->jsonSerialize();
}

View File

@@ -27,6 +27,31 @@ class AmqpHandler extends AbstractProcessingHandler
* @var AMQPExchange|AMQPChannel $exchange
*/
protected $exchange;
/** @var array<string, mixed> */
private $extraAttributes = [];
/**
* @return array<string, mixed>
*/
public function getExtraAttributes(): array
{
return $this->extraAttributes;
}
/**
* Configure extra attributes to pass to the AMQPExchange (if you are using the amqp extension)
*
* @param array<string, mixed> $extraAttributes One of content_type, content_encoding,
* message_id, user_id, app_id, delivery_mode,
* priority, timestamp, expiration, type
* or reply_to, headers.
* @return AmqpHandler
*/
public function setExtraAttributes(array $extraAttributes): self
{
$this->extraAttributes = $extraAttributes;
return $this;
}
/**
* @var string
@@ -60,14 +85,18 @@ class AmqpHandler extends AbstractProcessingHandler
$routingKey = $this->getRoutingKey($record);
if ($this->exchange instanceof AMQPExchange) {
$attributes = [
'delivery_mode' => 2,
'content_type' => 'application/json',
];
if ($this->extraAttributes) {
$attributes = array_merge($attributes, $this->extraAttributes);
}
$this->exchange->publish(
$data,
$routingKey,
0,
[
'delivery_mode' => 2,
'content_type' => 'application/json',
]
$attributes
);
} else {
$this->exchange->basic_publish(
@@ -122,13 +151,14 @@ class AmqpHandler extends AbstractProcessingHandler
private function createAmqpMessage(string $data): AMQPMessage
{
return new AMQPMessage(
$data,
[
'delivery_mode' => 2,
'content_type' => 'application/json',
]
);
$attributes = [
'delivery_mode' => 2,
'content_type' => 'application/json',
];
if ($this->extraAttributes) {
$attributes = array_merge($attributes, $this->extraAttributes);
}
return new AMQPMessage($data, $attributes);
}
/**

View File

@@ -14,6 +14,7 @@ namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Monolog\Utils;
use Monolog\Logger;
use function count;
use function headers_list;
@@ -177,7 +178,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
$extra = static::dump('Extra', $record['extra']);
if (empty($context) && empty($extra)) {
$script[] = static::call_array('log', static::handleStyles($record['formatted']));
$script[] = static::call_array(static::getConsoleMethodForLevel($record['level']), static::handleStyles($record['formatted']));
} else {
$script = array_merge(
$script,
@@ -192,6 +193,20 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);";
}
private static function getConsoleMethodForLevel(int $level): string
{
return [
Logger::DEBUG => 'debug',
Logger::INFO => 'info',
Logger::NOTICE => 'info',
Logger::WARNING => 'warn',
Logger::ERROR => 'error',
Logger::CRITICAL => 'error',
Logger::ALERT => 'error',
Logger::EMERGENCY => 'error',
][$level] ?? 'log';
}
/**
* @return string[]
*/

View File

@@ -149,7 +149,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
}
$json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true);
$data = base64_encode(utf8_encode($json));
$data = base64_encode($json);
if (strlen($data) > 3 * 1024) {
self::$overflowed = true;
@@ -163,8 +163,8 @@ class ChromePHPHandler extends AbstractProcessingHandler
'extra' => [],
];
self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record);
$json = Utils::jsonEncode(self::$json, null, true);
$data = base64_encode(utf8_encode($json));
$json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true);
$data = base64_encode($json);
}
if (trim($data) !== '') {

View File

@@ -17,8 +17,9 @@ use Monolog\Utils;
/**
* Logs to Cube.
*
* @link http://square.github.com/cube/
* @link https://github.com/square/cube/wiki
* @author Wan Chen <kami@kamisama.me>
* @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4
*/
class CubeHandler extends AbstractProcessingHandler
{

View File

@@ -11,6 +11,7 @@
namespace Monolog\Handler;
use Elastic\Elasticsearch\Response\Elasticsearch;
use Throwable;
use RuntimeException;
use Monolog\Logger;
@@ -19,6 +20,8 @@ use Monolog\Formatter\ElasticsearchFormatter;
use InvalidArgumentException;
use Elasticsearch\Common\Exceptions\RuntimeException as ElasticsearchRuntimeException;
use Elasticsearch\Client;
use Elastic\Elasticsearch\Exception\InvalidArgumentException as ElasticInvalidArgumentException;
use Elastic\Elasticsearch\Client as Client8;
/**
* Elasticsearch handler
@@ -44,7 +47,7 @@ use Elasticsearch\Client;
class ElasticsearchHandler extends AbstractProcessingHandler
{
/**
* @var Client
* @var Client|Client8
*/
protected $client;
@@ -54,11 +57,20 @@ class ElasticsearchHandler extends AbstractProcessingHandler
protected $options = [];
/**
* @param Client $client Elasticsearch Client object
* @param mixed[] $options Handler configuration
* @var bool
*/
public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, bool $bubble = true)
private $needsType;
/**
* @param Client|Client8 $client Elasticsearch Client object
* @param mixed[] $options Handler configuration
*/
public function __construct($client, array $options = [], $level = Logger::DEBUG, bool $bubble = true)
{
if (!$client instanceof Client && !$client instanceof Client8) {
throw new \TypeError('Elasticsearch\Client or Elastic\Elasticsearch\Client instance required');
}
parent::__construct($level, $bubble);
$this->client = $client;
$this->options = array_merge(
@@ -69,6 +81,14 @@ class ElasticsearchHandler extends AbstractProcessingHandler
],
$options
);
if ($client instanceof Client8 || $client::VERSION[0] === '7') {
$this->needsType = false;
// force the type to _doc for ES8/ES7
$this->options['type'] = '_doc';
} else {
$this->needsType = true;
}
}
/**
@@ -133,9 +153,11 @@ class ElasticsearchHandler extends AbstractProcessingHandler
foreach ($records as $record) {
$params['body'][] = [
'index' => [
'index' => $this->needsType ? [
'_index' => $record['_index'],
'_type' => $record['_type'],
] : [
'_index' => $record['_index'],
],
];
unset($record['_index'], $record['_type']);
@@ -143,6 +165,7 @@ class ElasticsearchHandler extends AbstractProcessingHandler
$params['body'][] = $record;
}
/** @var Elasticsearch */
$responses = $this->client->bulk($params);
if ($responses['errors'] === true) {
@@ -160,9 +183,9 @@ class ElasticsearchHandler extends AbstractProcessingHandler
*
* Only the first error is converted into an exception.
*
* @param mixed[] $responses returned by $this->client->bulk()
* @param mixed[]|Elasticsearch $responses returned by $this->client->bulk()
*/
protected function createExceptionFromResponses(array $responses): ElasticsearchRuntimeException
protected function createExceptionFromResponses($responses): Throwable
{
foreach ($responses['items'] ?? [] as $item) {
if (isset($item['index']['error'])) {
@@ -170,6 +193,10 @@ class ElasticsearchHandler extends AbstractProcessingHandler
}
}
if (class_exists(ElasticInvalidArgumentException::class)) {
return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.');
}
return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.');
}
@@ -178,10 +205,14 @@ class ElasticsearchHandler extends AbstractProcessingHandler
*
* @param mixed[] $error
*/
protected function createExceptionFromError(array $error): ElasticsearchRuntimeException
protected function createExceptionFromError(array $error): Throwable
{
$previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null;
if (class_exists(ElasticInvalidArgumentException::class)) {
return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous);
}
return new ElasticsearchRuntimeException($error['type'] . ': ' . $error['reason'], 0, $previous);
}
}

View File

@@ -161,7 +161,7 @@ class FilterHandler extends Handler implements ProcessableHandlerInterface, Rese
*
* @phpstan-param Record $record
*/
public function getHandler(array $record = null)
public function getHandler(?array $record = null)
{
if (!$this->handler instanceof HandlerInterface) {
$this->handler = ($this->handler)($record, $this);

View File

@@ -210,7 +210,7 @@ class FingersCrossedHandler extends Handler implements ProcessableHandlerInterfa
*
* @phpstan-param Record $record
*/
public function getHandler(array $record = null)
public function getHandler(?array $record = null)
{
if (!$this->handler instanceof HandlerInterface) {
$this->handler = ($this->handler)($record, $this);

View File

@@ -28,6 +28,7 @@ use Monolog\Formatter\FormatterInterface;
* @see https://www.flowdock.com/api/push
*
* @phpstan-import-type FormattedRecord from AbstractProcessingHandler
* @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4
*/
class FlowdockHandler extends SocketHandler
{

View File

@@ -48,6 +48,15 @@ abstract class Handler implements HandlerInterface
{
$this->close();
return array_keys(get_object_vars($this));
$reflClass = new \ReflectionClass($this);
$keys = [];
foreach ($reflClass->getProperties() as $reflProp) {
if (!$reflProp->isStatic()) {
$keys[] = $reflProp->getName();
}
}
return $keys;
}
}

View File

@@ -25,7 +25,7 @@ use PhpConsole\Helper;
* Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely
*
* Usage:
* 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef
* 1. Install Google Chrome extension [now dead and removed from the chrome store]
* 2. See overview https://github.com/barbushin/php-console#overview
* 3. Install PHP Console library https://github.com/barbushin/php-console#installation
* 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png)
@@ -39,6 +39,7 @@ use PhpConsole\Helper;
* @author Sergey Barbushin https://www.linkedin.com/in/barbushin
*
* @phpstan-import-type Record from \Monolog\Logger
* @deprecated Since 2.8.0 and 3.2.0, PHPConsole is abandoned and thus we will drop this handler in Monolog 4
*/
class PHPConsoleHandler extends AbstractProcessingHandler
{

View File

@@ -30,7 +30,7 @@ use Monolog\Logger;
*/
class RedisHandler extends AbstractProcessingHandler
{
/** @var \Predis\Client|\Redis */
/** @var \Predis\Client<\Predis\Client>|\Redis */
private $redisClient;
/** @var string */
private $redisKey;
@@ -38,7 +38,7 @@ class RedisHandler extends AbstractProcessingHandler
protected $capSize;
/**
* @param \Predis\Client|\Redis $redis The redis instance
* @param \Predis\Client<\Predis\Client>|\Redis $redis The redis instance
* @param string $key The key name to push records to
* @param int $capSize Number of entries to limit list size to, 0 = unlimited
*/

View File

@@ -28,13 +28,13 @@ use Monolog\Logger;
*/
class RedisPubSubHandler extends AbstractProcessingHandler
{
/** @var \Predis\Client|\Redis */
/** @var \Predis\Client<\Predis\Client>|\Redis */
private $redisClient;
/** @var string */
private $channelKey;
/**
* @param \Predis\Client|\Redis $redis The redis instance
* @param \Predis\Client<\Predis\Client>|\Redis $redis The redis instance
* @param string $key The channel key to publish records to
*/
public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true)

View File

@@ -191,7 +191,11 @@ class RotatingFileHandler extends StreamHandler
$fileInfo = pathinfo($this->filename);
$glob = str_replace(
['{filename}', '{date}'],
[$fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'],
[$fileInfo['filename'], str_replace(
['Y', 'y', 'm', 'd'],
['[0-9][0-9][0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]'],
$this->dateFormat)
],
$fileInfo['dirname'] . '/' . $this->filenameFormat
);
if (isset($fileInfo['extension'])) {

View File

@@ -90,7 +90,7 @@ class SamplingHandler extends AbstractHandler implements ProcessableHandlerInter
*
* @return HandlerInterface
*/
public function getHandler(array $record = null)
public function getHandler(?array $record = null)
{
if (!$this->handler instanceof HandlerInterface) {
$this->handler = ($this->handler)($record, $this);

View File

@@ -100,7 +100,7 @@ class SlackRecord
bool $useShortAttachment = false,
bool $includeContextAndExtra = false,
array $excludeFields = array(),
FormatterInterface $formatter = null
?FormatterInterface $formatter = null
) {
$this
->setChannel($channel)

View File

@@ -135,11 +135,14 @@ class StreamHandler extends AbstractProcessingHandler
$this->createDir($url);
$this->errorMessage = null;
set_error_handler([$this, 'customErrorHandler']);
$stream = fopen($url, 'a');
if ($this->filePermission !== null) {
@chmod($url, $this->filePermission);
try {
$stream = fopen($url, 'a');
if ($this->filePermission !== null) {
@chmod($url, $this->filePermission);
}
} finally {
restore_error_handler();
}
restore_error_handler();
if (!is_resource($stream)) {
$this->stream = null;
@@ -212,7 +215,7 @@ class StreamHandler extends AbstractProcessingHandler
set_error_handler([$this, 'customErrorHandler']);
$status = mkdir($dir, 0777, true);
restore_error_handler();
if (false === $status && !is_dir($dir)) {
if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage, 'File exists') === false) {
throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage, $dir));
}
}

View File

@@ -24,6 +24,7 @@ use Swift;
* @author Gyula Sallai
*
* @phpstan-import-type Record from \Monolog\Logger
* @deprecated Since Monolog 2.6. Use SymfonyMailerHandler instead.
*/
class SwiftMailerHandler extends MailHandler
{
@@ -42,6 +43,8 @@ class SwiftMailerHandler extends MailHandler
{
parent::__construct($level, $bubble);
@trigger_error('The SwiftMailerHandler is deprecated since Monolog 2.6. Use SymfonyMailerHandler instead.', E_USER_DEPRECATED);
$this->mailer = $mailer;
$this->messageTemplate = $message;
}

View File

@@ -23,20 +23,12 @@ class UdpSocket
/** @var int */
protected $port;
/** @var resource|Socket|null */
protected $socket;
protected $socket = null;
public function __construct(string $ip, int $port = 514)
{
$this->ip = $ip;
$this->port = $port;
$domain = AF_INET;
$protocol = SOL_UDP;
// Check if we are using unix sockets.
if ($port === 0) {
$domain = AF_UNIX;
$protocol = IPPROTO_IP;
}
$this->socket = socket_create($domain, SOCK_DGRAM, $protocol) ?: null;
}
/**
@@ -57,12 +49,34 @@ class UdpSocket
}
}
/**
* @return resource|Socket
*/
protected function getSocket()
{
if (null !== $this->socket) {
return $this->socket;
}
$domain = AF_INET;
$protocol = SOL_UDP;
// Check if we are using unix sockets.
if ($this->port === 0) {
$domain = AF_UNIX;
$protocol = IPPROTO_IP;
}
$this->socket = socket_create($domain, SOCK_DGRAM, $protocol) ?: null;
if (null === $this->socket) {
throw new \RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' could not be opened via socket_create');
}
return $this->socket;
}
protected function send(string $chunk): void
{
if (!is_resource($this->socket) && !$this->socket instanceof Socket) {
throw new \RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore');
}
socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port);
socket_sendto($this->getSocket(), $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port);
}
protected function assembleMessage(string $line, string $header): string

View File

@@ -108,9 +108,9 @@ class TelegramBotHandler extends AbstractProcessingHandler
string $channel,
$level = Logger::DEBUG,
bool $bubble = true,
string $parseMode = null,
bool $disableWebPagePreview = null,
bool $disableNotification = null,
?string $parseMode = null,
?bool $disableWebPagePreview = null,
?bool $disableNotification = null,
bool $splitLongMessages = false,
bool $delayBetweenMessages = false
)
@@ -130,7 +130,7 @@ class TelegramBotHandler extends AbstractProcessingHandler
$this->delayBetweenMessages($delayBetweenMessages);
}
public function setParseMode(string $parseMode = null): self
public function setParseMode(?string $parseMode = null): self
{
if ($parseMode !== null && !in_array($parseMode, self::AVAILABLE_PARSE_MODES)) {
throw new \InvalidArgumentException('Unknown parseMode, use one of these: ' . implode(', ', self::AVAILABLE_PARSE_MODES) . '.');
@@ -141,14 +141,14 @@ class TelegramBotHandler extends AbstractProcessingHandler
return $this;
}
public function disableWebPagePreview(bool $disableWebPagePreview = null): self
public function disableWebPagePreview(?bool $disableWebPagePreview = null): self
{
$this->disableWebPagePreview = $disableWebPagePreview;
return $this;
}
public function disableNotification(bool $disableNotification = null): self
public function disableNotification(?bool $disableNotification = null): self
{
$this->disableNotification = $disableNotification;

View File

@@ -64,4 +64,18 @@ class WhatFailureGroupHandler extends GroupHandler
}
}
}
/**
* {@inheritDoc}
*/
public function close(): void
{
foreach ($this->handlers as $handler) {
try {
$handler->close();
} catch (\Throwable $e) {
// What failure?
}
}
}
}

View File

@@ -111,6 +111,22 @@ class Logger implements LoggerInterface, ResettableInterface
self::EMERGENCY => 'EMERGENCY',
];
/**
* Mapping between levels numbers defined in RFC 5424 and Monolog ones
*
* @phpstan-var array<int, Level> $rfc_5424_levels
*/
private const RFC_5424_LEVELS = [
7 => self::DEBUG,
6 => self::INFO,
5 => self::NOTICE,
4 => self::WARNING,
3 => self::ERROR,
2 => self::CRITICAL,
1 => self::ALERT,
0 => self::EMERGENCY,
];
/**
* @var string
*/
@@ -147,6 +163,23 @@ class Logger implements LoggerInterface, ResettableInterface
*/
protected $exceptionHandler;
/**
* @var int Keeps track of depth to prevent infinite logging loops
*/
private $logDepth = 0;
/**
* @var \WeakMap<\Fiber, int>|null Keeps track of depth inside fibers to prevent infinite logging loops
*/
private $fiberLogDepth;
/**
* @var bool Whether to detect infinite logging loops
*
* This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this
*/
private $detectCycles = true;
/**
* @psalm-param array<callable(array): array> $processors
*
@@ -161,6 +194,13 @@ class Logger implements LoggerInterface, ResettableInterface
$this->setHandlers($handlers);
$this->processors = $processors;
$this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC');
if (\PHP_VERSION_ID >= 80100) {
// Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412
/** @var \WeakMap<\Fiber, int> $fiberLogDepth */
$fiberLogDepth = new \WeakMap();
$this->fiberLogDepth = $fiberLogDepth;
}
}
public function getName(): string
@@ -279,42 +319,85 @@ class Logger implements LoggerInterface, ResettableInterface
return $this;
}
public function useLoggingLoopDetection(bool $detectCycles): self
{
$this->detectCycles = $detectCycles;
return $this;
}
/**
* Adds a log record.
*
* @param int $level The logging level
* @param string $message The log message
* @param mixed[] $context The log context
* @return bool Whether the record has been processed
* @param int $level The logging level (a Monolog or RFC 5424 level)
* @param string $message The log message
* @param mixed[] $context The log context
* @param DateTimeImmutable $datetime Optional log date to log into the past or future
* @return bool Whether the record has been processed
*
* @phpstan-param Level $level
*/
public function addRecord(int $level, string $message, array $context = []): bool
public function addRecord(int $level, string $message, array $context = [], ?DateTimeImmutable $datetime = null): bool
{
$record = null;
if (isset(self::RFC_5424_LEVELS[$level])) {
$level = self::RFC_5424_LEVELS[$level];
}
foreach ($this->handlers as $handler) {
if (null === $record) {
// skip creating the record as long as no handler is going to handle it
if (!$handler->isHandling(['level' => $level])) {
continue;
if ($this->detectCycles) {
if (\PHP_VERSION_ID >= 80100 && $fiber = \Fiber::getCurrent()) {
$this->fiberLogDepth[$fiber] = $this->fiberLogDepth[$fiber] ?? 0;
$logDepth = ++$this->fiberLogDepth[$fiber];
} else {
$logDepth = ++$this->logDepth;
}
} else {
$logDepth = 0;
}
if ($logDepth === 3) {
$this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.');
return false;
} elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above
return false;
}
try {
$record = null;
foreach ($this->handlers as $handler) {
if (null === $record) {
// skip creating the record as long as no handler is going to handle it
if (!$handler->isHandling(['level' => $level])) {
continue;
}
$levelName = static::getLevelName($level);
$record = [
'message' => $message,
'context' => $context,
'level' => $level,
'level_name' => $levelName,
'channel' => $this->name,
'datetime' => $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone),
'extra' => [],
];
try {
foreach ($this->processors as $processor) {
$record = $processor($record);
}
} catch (Throwable $e) {
$this->handleException($e, $record);
return true;
}
}
$levelName = static::getLevelName($level);
$record = [
'message' => $message,
'context' => $context,
'level' => $level,
'level_name' => $levelName,
'channel' => $this->name,
'datetime' => new DateTimeImmutable($this->microsecondTimestamps, $this->timezone),
'extra' => [],
];
// once the record exists, send it to all handlers as long as the bubbling chain is not interrupted
try {
foreach ($this->processors as $processor) {
$record = $processor($record);
if (true === $handler->handle($record)) {
break;
}
} catch (Throwable $e) {
$this->handleException($e, $record);
@@ -322,16 +405,13 @@ class Logger implements LoggerInterface, ResettableInterface
return true;
}
}
// once the record exists, send it to all handlers as long as the bubbling chain is not interrupted
try {
if (true === $handler->handle($record)) {
break;
} finally {
if ($this->detectCycles) {
if (isset($fiber)) {
$this->fiberLogDepth[$fiber]--;
} else {
$this->logDepth--;
}
} catch (Throwable $e) {
$this->handleException($e, $record);
return true;
}
}
@@ -484,7 +564,7 @@ class Logger implements LoggerInterface, ResettableInterface
*
* This method allows for compatibility with common interfaces.
*
* @param mixed $level The log level
* @param mixed $level The log level (a Monolog, PSR-3 or RFC 5424 level)
* @param string|Stringable $message The log message
* @param mixed[] $context The log context
*
@@ -496,6 +576,10 @@ class Logger implements LoggerInterface, ResettableInterface
throw new \InvalidArgumentException('$level is expected to be a string or int');
}
if (isset(self::RFC_5424_LEVELS[$level])) {
$level = self::RFC_5424_LEVELS[$level];
}
$level = static::toMonologLevel($level);
$this->addRecord($level, (string) $message, $context);
@@ -638,4 +722,40 @@ class Logger implements LoggerInterface, ResettableInterface
($this->exceptionHandler)($e, $record);
}
/**
* @return array<string, mixed>
*/
public function __serialize(): array
{
return [
'name' => $this->name,
'handlers' => $this->handlers,
'processors' => $this->processors,
'microsecondTimestamps' => $this->microsecondTimestamps,
'timezone' => $this->timezone,
'exceptionHandler' => $this->exceptionHandler,
'logDepth' => $this->logDepth,
'detectCycles' => $this->detectCycles,
];
}
/**
* @param array<string, mixed> $data
*/
public function __unserialize(array $data): void
{
foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) {
if (isset($data[$property])) {
$this->$property = $data[$property];
}
}
if (\PHP_VERSION_ID >= 80100) {
// Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412
/** @var \WeakMap<\Fiber, int> $fiberLogDepth */
$fiberLogDepth = new \WeakMap();
$this->fiberLogDepth = $fiberLogDepth;
}
}
}

View File

@@ -66,6 +66,8 @@ class PsrLogMessageProcessor implements ProcessorInterface
} else {
$replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE);
}
} elseif ($val instanceof \UnitEnum) {
$replacements[$placeholder] = $val instanceof \BackedEnum ? $val->value : $val->name;
} elseif (is_object($val)) {
$replacements[$placeholder] = '[object '.Utils::getClass($val).']';
} elseif (is_array($val)) {

View File

@@ -43,7 +43,7 @@ class WebProcessor implements ProcessorInterface
* @param array<string, mixed>|\ArrayAccess<string, mixed>|null $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data
* @param array<string, string>|array<string>|null $extraFields Field names and the related key inside $serverData to be added (or just a list of field names to use the default configured $serverData mapping). If not provided it defaults to: [url, ip, http_method, server, referrer] + unique_id if present in server data
*/
public function __construct($serverData = null, array $extraFields = null)
public function __construct($serverData = null, ?array $extraFields = null)
{
if (null === $serverData) {
$this->serverData = &$_SERVER;

View File

@@ -22,9 +22,20 @@ use Monolog\Formatter\FormatterInterface;
*
* @phpstan-import-type Record from \Monolog\Logger
* @phpstan-import-type Level from \Monolog\Logger
*
* @internal feel free to reuse this to test your own handlers, this is marked internal to avoid issues with PHPStorm https://github.com/Seldaek/monolog/issues/1677
*/
class TestCase extends \PHPUnit\Framework\TestCase
{
public function tearDown(): void
{
parent::tearDown();
if (isset($this->handler)) {
unset($this->handler);
}
}
/**
* @param mixed[] $context
*

View File

@@ -211,7 +211,7 @@ final class Utils
$data = preg_replace_callback(
'/[\x80-\xFF]+/',
function ($m) {
return utf8_encode($m[0]);
return function_exists('mb_convert_encoding') ? mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') : utf8_encode($m[0]);
},
$data
);