From d61d5d1e0953e31c7e07b2b383b61c10b3ea2e44 Mon Sep 17 00:00:00 2001 From: Emmanuel Asuquo Date: Fri, 17 Apr 2026 16:49:52 -0400 Subject: [PATCH 1/3] Monolog dependency fixed to lastest and Export to word Document fixed --- composer.json | 2 +- composer.lock | 66 +- docker/dev/Dockerfile | 1 + htdocs/export/export_word.php | 55 +- htdocs/export/export_word_aggregate.php | 50 +- htdocs/export/word_export_lib.php | 93 +++ vendor/composer/installed.json | 66 +- vendor/composer/installed.php | 8 +- vendor/composer/platform_check.php | 4 +- vendor/monolog/monolog/CHANGELOG.md | 234 ++++++- vendor/monolog/monolog/LICENSE | 2 +- vendor/monolog/monolog/README.md | 42 +- vendor/monolog/monolog/UPGRADE.md | 72 ++ vendor/monolog/monolog/composer.json | 61 +- vendor/monolog/monolog/phpstan.neon.dist | 16 - .../Monolog/Attribute/AsMonologProcessor.php | 46 ++ .../monolog/src/Monolog/DateTimeImmutable.php | 51 ++ .../monolog/src/Monolog/ErrorHandler.php | 186 +++-- .../Monolog/Formatter/ChromePHPFormatter.php | 21 +- .../Monolog/Formatter/ElasticaFormatter.php | 36 +- .../Formatter/ElasticsearchFormatter.php | 89 +++ .../Monolog/Formatter/FlowdockFormatter.php | 38 +- .../Monolog/Formatter/FluentdFormatter.php | 18 +- .../Monolog/Formatter/FormatterInterface.php | 8 +- .../Formatter/GelfMessageFormatter.php | 103 ++- .../Formatter/GoogleCloudLoggingFormatter.php | 40 ++ .../src/Monolog/Formatter/HtmlFormatter.php | 64 +- .../src/Monolog/Formatter/JsonFormatter.php | 152 +++-- .../src/Monolog/Formatter/LineFormatter.php | 139 +++- .../src/Monolog/Formatter/LogglyFormatter.php | 12 +- .../Monolog/Formatter/LogmaticFormatter.php | 66 ++ .../Monolog/Formatter/LogstashFormatter.php | 115 +--- .../Monolog/Formatter/MongoDBFormatter.php | 81 ++- .../Monolog/Formatter/NormalizerFormatter.php | 185 +++-- .../src/Monolog/Formatter/ScalarFormatter.php | 19 +- .../Monolog/Formatter/WildfireFormatter.php | 60 +- .../src/Monolog/Handler/AbstractHandler.php | 136 +--- .../Handler/AbstractProcessingHandler.php | 45 +- .../Monolog/Handler/AbstractSyslogHandler.php | 29 +- .../src/Monolog/Handler/AmqpHandler.php | 99 +-- .../Monolog/Handler/BrowserConsoleHandler.php | 161 +++-- .../src/Monolog/Handler/BufferHandler.php | 69 +- .../src/Monolog/Handler/ChromePHPHandler.php | 112 ++- .../src/Monolog/Handler/CouchDBHandler.php | 27 +- .../src/Monolog/Handler/CubeHandler.php | 67 +- .../monolog/src/Monolog/Handler/Curl/Util.php | 38 +- .../Monolog/Handler/DeduplicationHandler.php | 41 +- .../Handler/DoctrineCouchDBHandler.php | 10 +- .../src/Monolog/Handler/DynamoDbHandler.php | 32 +- .../Monolog/Handler/ElasticSearchHandler.php | 177 +++-- .../src/Monolog/Handler/ElasticaHandler.php | 129 ++++ .../src/Monolog/Handler/ErrorLogHandler.php | 49 +- .../Monolog/Handler/FallbackGroupHandler.php | 71 ++ .../src/Monolog/Handler/FilterHandler.php | 94 ++- .../ActivationStrategyInterface.php | 9 +- .../ChannelLevelActivationStrategy.php | 28 +- .../ErrorLevelActivationStrategy.php | 16 +- .../Monolog/Handler/FingersCrossedHandler.php | 117 +++- .../src/Monolog/Handler/FirePHPHandler.php | 95 ++- .../src/Monolog/Handler/FleepHookHandler.php | 67 +- .../src/Monolog/Handler/FlowdockHandler.php | 63 +- .../Handler/FormattableHandlerInterface.php | 2 - .../Handler/FormattableHandlerTrait.php | 9 +- .../src/Monolog/Handler/GelfHandler.php | 24 +- .../src/Monolog/Handler/GroupHandler.php | 61 +- .../monolog/src/Monolog/Handler/Handler.php | 62 ++ .../src/Monolog/Handler/HandlerInterface.php | 59 +- .../src/Monolog/Handler/HandlerWrapper.php | 76 ++- .../src/Monolog/Handler/HipChatHandler.php | 367 ---------- .../src/Monolog/Handler/IFTTTHandler.php | 24 +- .../src/Monolog/Handler/InsightOpsHandler.php | 48 +- .../src/Monolog/Handler/LogEntriesHandler.php | 41 +- .../src/Monolog/Handler/LogglyHandler.php | 112 ++- .../src/Monolog/Handler/LogmaticHandler.php | 106 +++ .../src/Monolog/Handler/MailHandler.php | 48 +- .../src/Monolog/Handler/MandrillHandler.php | 41 +- .../Handler/MissingExtensionException.php | 6 +- .../src/Monolog/Handler/MongoDBHandler.php | 64 +- .../Monolog/Handler/NativeMailerHandler.php | 77 +-- .../src/Monolog/Handler/NewRelicHandler.php | 50 +- .../src/Monolog/Handler/NoopHandler.php | 40 ++ .../src/Monolog/Handler/NullHandler.php | 35 +- .../src/Monolog/Handler/OverflowHandler.php | 149 ++++ .../src/Monolog/Handler/PHPConsoleHandler.php | 96 +-- .../src/Monolog/Handler/ProcessHandler.php | 191 ++++++ .../Handler/ProcessableHandlerInterface.php | 14 +- .../Handler/ProcessableHandlerTrait.php | 16 +- .../src/Monolog/Handler/PsrHandler.php | 53 +- .../src/Monolog/Handler/PushoverHandler.php | 127 +++- .../src/Monolog/Handler/RavenHandler.php | 234 ------- .../src/Monolog/Handler/RedisHandler.php | 25 +- .../Monolog/Handler/RedisPubSubHandler.php | 67 ++ .../src/Monolog/Handler/RollbarHandler.php | 69 +- .../Monolog/Handler/RotatingFileHandler.php | 109 +-- .../src/Monolog/Handler/SamplingHandler.php | 57 +- .../src/Monolog/Handler/SendGridHandler.php | 102 +++ .../src/Monolog/Handler/Slack/SlackRecord.php | 218 ++++-- .../src/Monolog/Handler/SlackHandler.php | 163 +++-- .../Monolog/Handler/SlackWebhookHandler.php | 57 +- .../src/Monolog/Handler/SlackbotHandler.php | 84 --- .../src/Monolog/Handler/SocketHandler.php | 211 ++++-- .../src/Monolog/Handler/SqsHandler.php | 62 ++ .../src/Monolog/Handler/StreamHandler.php | 172 +++-- .../Monolog/Handler/SwiftMailerHandler.php | 72 +- .../Monolog/Handler/SymfonyMailerHandler.php | 111 +++ .../src/Monolog/Handler/SyslogHandler.php | 25 +- .../Monolog/Handler/SyslogUdp/UdpSocket.php | 60 +- .../src/Monolog/Handler/SyslogUdpHandler.php | 84 ++- .../Monolog/Handler/TelegramBotHandler.php | 278 ++++++++ .../src/Monolog/Handler/TestHandler.php | 98 ++- .../Handler/WebRequestRecognizerTrait.php | 24 + .../Handler/WhatFailureGroupHandler.php | 39 +- .../Monolog/Handler/ZendMonitorHandler.php | 46 +- .../monolog/monolog/src/Monolog/LogRecord.php | 34 + vendor/monolog/monolog/src/Monolog/Logger.php | 642 +++++++++--------- .../src/Monolog/Processor/GitProcessor.php | 33 +- .../Monolog/Processor/HostnameProcessor.php | 36 + .../Processor/IntrospectionProcessor.php | 51 +- .../Processor/MemoryPeakUsageProcessor.php | 16 +- .../src/Monolog/Processor/MemoryProcessor.php | 14 +- .../Processor/MemoryUsageProcessor.php | 16 +- .../Monolog/Processor/MercurialProcessor.php | 36 +- .../Monolog/Processor/ProcessIdProcessor.php | 7 +- .../Monolog/Processor/ProcessorInterface.php | 11 +- .../Processor/PsrLogMessageProcessor.php | 25 +- .../src/Monolog/Processor/TagProcessor.php | 27 +- .../src/Monolog/Processor/UidProcessor.php | 22 +- .../src/Monolog/Processor/WebProcessor.php | 56 +- .../monolog/monolog/src/Monolog/Registry.php | 28 +- .../src/Monolog/ResettableInterface.php | 5 +- .../monolog/src/Monolog/SignalHandler.php | 94 +-- .../monolog/src/Monolog/Test/TestCase.php | 85 +++ vendor/monolog/monolog/src/Monolog/Utils.php | 151 +++- 133 files changed, 6462 insertions(+), 3474 deletions(-) create mode 100644 htdocs/export/word_export_lib.php create mode 100644 vendor/monolog/monolog/UPGRADE.md delete mode 100644 vendor/monolog/monolog/phpstan.neon.dist create mode 100644 vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/Handler.php delete mode 100644 vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php delete mode 100644 vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php delete mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php create mode 100644 vendor/monolog/monolog/src/Monolog/LogRecord.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Test/TestCase.php diff --git a/composer.json b/composer.json index 8e5f4d13a..3249a59d2 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "require": { - "monolog/monolog": "^1.25", + "monolog/monolog": "^2.11", "phpoffice/phpexcel": "= 1.8.2" } } diff --git a/composer.lock b/composer.lock index 0fb86e908..c49fc7a88 100644 --- a/composer.lock +++ b/composer.lock @@ -4,55 +4,71 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "28354d1d13e43da729e1e7a74044f5bc", + "content-hash": "e8c0e7483b2705193c41ba441f0f0973", "packages": [ { "name": "monolog/monolog", - "version": "1.27.1", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "904713c5929655dc9b97288b69cfeedad610c9a1" + "reference": "37308608e599f34a1a4845b16440047ec98a172a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/904713c5929655dc9b97288b69cfeedad610c9a1", - "reference": "904713c5929655dc9b97288b69cfeedad610c9a1", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/37308608e599f34a1a4845b16440047ec98a172a", + "reference": "37308608e599f34a1a4845b16440047ec98a172a", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" }, "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpstan/phpstan": "^0.12.59", - "phpunit/phpunit": "~4.5", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "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": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, "autoload": { "psr-4": { "Monolog\\": "src/Monolog" @@ -66,11 +82,11 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", + "homepage": "https://github.com/Seldaek/monolog", "keywords": [ "log", "logging", @@ -78,7 +94,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/1.27.1" + "source": "https://github.com/Seldaek/monolog/tree/2.11.0" }, "funding": [ { @@ -90,7 +106,7 @@ "type": "tidelift" } ], - "time": "2022-06-09T08:53:42+00:00" + "time": "2026-01-01T13:05:00+00:00" }, { "name": "phpoffice/phpexcel", diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 4cdd71f33..94f7809e0 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -20,6 +20,7 @@ RUN apt-get update && apt-get install -y software-properties-common && \ apache2 \ curl \ mysql-client \ + pandoc \ unzip \ zip \ php$PHP_VERSION \ diff --git a/htdocs/export/export_word.php b/htdocs/export/export_word.php index 1403535f5..8ecb73a59 100755 --- a/htdocs/export/export_word.php +++ b/htdocs/export/export_word.php @@ -1,27 +1,28 @@ - - - - - -Saves as a Word Doc - - -" . stripcslashes($_REQUEST['data']); -print $html_content; -?> - - \ No newline at end of file + 0) +{ + $logo_file = __DIR__."/../logos/logo_".$id.".jpg"; + if(is_file($logo_file)) + { + # Pandoc handles local filesystem image references from HTML. + $html_content = "\n".$html_content; + } +} + +$exported = blis_word_export_docx($html_content, "blisreport"); +if($exported === false) +{ + # Keep backward compatibility if pandoc is unavailable. + blis_word_send_legacy_doc($html_content, "blisreport"); +} diff --git a/htdocs/export/export_word_aggregate.php b/htdocs/export/export_word_aggregate.php index 7c2f16d4d..c2292f38a 100755 --- a/htdocs/export/export_word_aggregate.php +++ b/htdocs/export/export_word_aggregate.php @@ -1,25 +1,25 @@ - - - - - -Saves as a Word Doc - - - - - \ No newline at end of file +\n"; + echo $html_fragment; + echo ""; + exit; +} + +function blis_word_export_docx($html_fragment, $file_prefix) +{ + $pandoc_bin = trim((string)shell_exec('command -v pandoc')); + if($pandoc_bin === '') + { + return false; + } + + $tmp_html = tempnam(sys_get_temp_dir(), 'blis_word_html_'); + $tmp_docx_base = tempnam(sys_get_temp_dir(), 'blis_word_docx_'); + if($tmp_html === false || $tmp_docx_base === false) + { + return false; + } + + $tmp_docx = $tmp_docx_base.'.docx'; + @unlink($tmp_docx_base); + + $full_html = "\n".$html_fragment.""; + $write_ok = (file_put_contents($tmp_html, $full_html) !== false); + if(!$write_ok) + { + @unlink($tmp_html); + return false; + } + + $cmd = escapeshellarg($pandoc_bin) + ." -f html -t docx" + ." -o ".escapeshellarg($tmp_docx) + ." ".escapeshellarg($tmp_html) + ." 2>&1"; + + $command_output = array(); + $exit_code = 0; + exec($cmd, $command_output, $exit_code); + + @unlink($tmp_html); + + if($exit_code !== 0 || !is_file($tmp_docx)) + { + @unlink($tmp_docx); + return false; + } + + $date = date('YmdHi'); + $file_name = blis_word_sanitize_filename_segment($file_prefix).'_'.$date.'.docx'; + header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document'); + header('Content-Disposition: attachment; filename="'.$file_name.'"'); + header('Content-Length: '.filesize($tmp_docx)); + readfile($tmp_docx); + @unlink($tmp_docx); + exit; +} + diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 36d7f86be..83d8e2a2d 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -2,53 +2,69 @@ "packages": [ { "name": "monolog/monolog", - "version": "1.27.1", - "version_normalized": "1.27.1.0", + "version": "2.11.0", + "version_normalized": "2.11.0.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "904713c5929655dc9b97288b69cfeedad610c9a1" + "reference": "37308608e599f34a1a4845b16440047ec98a172a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/904713c5929655dc9b97288b69cfeedad610c9a1", - "reference": "904713c5929655dc9b97288b69cfeedad610c9a1", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/37308608e599f34a1a4845b16440047ec98a172a", + "reference": "37308608e599f34a1a4845b16440047ec98a172a", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" }, "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpstan/phpstan": "^0.12.59", - "phpunit/phpunit": "~4.5", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "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": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, - "time": "2022-06-09T08:53:42+00:00", + "time": "2026-01-01T13:05:00+00:00", "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, "installation-source": "dist", "autoload": { "psr-4": { @@ -63,11 +79,11 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", + "homepage": "https://github.com/Seldaek/monolog", "keywords": [ "log", "logging", @@ -75,7 +91,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/1.27.1" + "source": "https://github.com/Seldaek/monolog/tree/2.11.0" }, "funding": [ { diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 7a76767b7..c173a369d 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -20,12 +20,12 @@ 'dev_requirement' => false, ), 'monolog/monolog' => array( - 'pretty_version' => '1.27.1', - 'version' => '1.27.1.0', + 'pretty_version' => '2.11.0', + 'version' => '2.11.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../monolog/monolog', 'aliases' => array(), - 'reference' => '904713c5929655dc9b97288b69cfeedad610c9a1', + 'reference' => '37308608e599f34a1a4845b16440047ec98a172a', 'dev_requirement' => false, ), 'phpoffice/phpexcel' => array( @@ -49,7 +49,7 @@ 'psr/log-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0.0', + 0 => '1.0.0 || 2.0.0 || 3.0.0', ), ), ), diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php index 7621d4ff9..589e9e770 100644 --- a/vendor/composer/platform_check.php +++ b/vendor/composer/platform_check.php @@ -4,8 +4,8 @@ $issues = array(); -if (!(PHP_VERSION_ID >= 50300)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 5.3.0". You are running ' . PHP_VERSION . '.'; +if (!(PHP_VERSION_ID >= 70200)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.'; } if ($issues) { diff --git a/vendor/monolog/monolog/CHANGELOG.md b/vendor/monolog/monolog/CHANGELOG.md index a92155ee3..4ecd1e0e8 100644 --- a/vendor/monolog/monolog/CHANGELOG.md +++ b/vendor/monolog/monolog/CHANGELOG.md @@ -1,11 +1,227 @@ -### 1.27.1 (2022-06-09) +### 2.11.0 (2026-01-01) - * Fixed MandrillHandler support for SwiftMailer 6 (#1676) - * Fixed StreamHandler chunk size (backport from #1552) + * Added support for mongodb/mongodb 2.0+ + * Fixed deprecation warnings in PHP 8.4/8.5 + * Fixed TelegramBotHandler sending empty messages (#1992) + * Fixed RotatingFileHandler unlink errors not being suppressed correctly (#1999) -### 1.27.0 (2022-03-13) +### 2.10.0 (2024-11-12) - * Added $maxDepth / setMaxDepth to NormalizerFormatter / JsonFormatter to configure the maximum depth if the default of 9 does not work for you (#1633) + * Added `$fileOpenMode` to `StreamHandler` to define a custom fopen mode to open the log file (#1913) + * Fixed `StreamHandler` handling of write failures so that it now closes/reopens the stream and retries the write once before failing (#1882) + * Fixed `StreamHandler` error handler causing issues if a stream handler triggers an error (#1866) + * Fixed `JsonFormatter` handling of incomplete classes (#1834) + * Fixed `RotatingFileHandler` bug where rotation could sometimes not happen correctly (#1905) + +### 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) + +### 2.4.0 (2022-03-14) + + * Added [`Monolog\LogRecord`](src/Monolog/LogRecord.php) interface that can be used to type-hint records like `array|\Monolog\LogRecord $record` to be forward compatible with the upcoming Monolog 3 changes + * Added `includeStacktraces` constructor params to LineFormatter & JsonFormatter (#1603) + * Added `persistent`, `timeout`, `writingTimeout`, `connectionTimeout`, `chunkSize` constructor params to SocketHandler and derivatives (#1600) + * Added `AsMonologProcessor` PHP attribute which can help autowiring / autoconfiguration of processors if frameworks / integrations decide to make use of it. This is useless when used purely with Monolog (#1637) + * Added support for keeping native BSON types as is in MongoDBFormatter (#1620) + * Added support for a `user_agent` key in WebProcessor, disabled by default but you can use it by configuring the $extraFields you want (#1613) + * Added support for username/userIcon in SlackWebhookHandler (#1617) + * Added extension points to BrowserConsoleHandler (#1593) + * Added record message/context/extra info to exceptions thrown when a StreamHandler cannot open its stream to avoid completely losing the data logged (#1630) + * Fixed error handler signature to accept a null $context which happens with internal PHP errors (#1614) + * Fixed a few setter methods not returning `self` (#1609) + * Fixed handling of records going over the max Telegram message length (#1616) + +### 2.3.5 (2021-10-01) + + * Fixed regression in StreamHandler since 2.3.3 on systems with the memory_limit set to >=20GB (#1592) + +### 2.3.4 (2021-09-15) + + * Fixed support for psr/log 3.x (#1589) + +### 2.3.3 (2021-09-14) + + * Fixed memory usage when using StreamHandler and calling stream_get_contents on the resource you passed to it (#1578, #1577) + * Fixed support for psr/log 2.x (#1587) + * Fixed some type annotations + +### 2.3.2 (2021-07-23) + + * Fixed compatibility with PHP 7.2 - 7.4 when experiencing PCRE errors (#1568) + +### 2.3.1 (2021-07-14) + + * Fixed Utils::getClass handling of anonymous classes not being fully compatible with PHP 8 (#1563) + * Fixed some `@inheritDoc` annotations having the wrong case + +### 2.3.0 (2021-07-05) + + * Added a ton of PHPStan type annotations as well as type aliases on Monolog\Logger for Record, Level and LevelName that you can import (#1557) + * Added ability to customize date format when using JsonFormatter (#1561) + * Fixed FilterHandler not calling reset on its internal handler when reset() is called on it (#1531) + * Fixed SyslogUdpHandler not setting the timezone correctly on DateTimeImmutable instances (#1540) + * Fixed StreamHandler thread safety - chunk size set to 2GB now to avoid interlacing when doing concurrent writes (#1553) + +### 2.2.0 (2020-12-14) + + * Added JSON_PARTIAL_OUTPUT_ON_ERROR to default json encoding flags, to avoid dropping entire context data or even records due to an invalid subset of it somewhere + * Added setDateFormat to NormalizerFormatter (and Line/Json formatters by extension) to allow changing this after object creation + * Added RedisPubSubHandler to log records to a Redis channel using PUBLISH + * Added support for Elastica 7, and deprecated the $type argument of ElasticaFormatter which is not in use anymore as of Elastica 7 + * Added support for millisecond write timeouts in SocketHandler, you can now pass floats to setWritingTimeout, e.g. 0.2 is 200ms + * Added support for unix sockets in SyslogUdpHandler (set $port to 0 to make the $host a unix socket) + * Added handleBatch support for TelegramBotHandler + * Added RFC5424e extended date format including milliseconds to SyslogUdpHandler + * Added support for configuring handlers with numeric level values in strings (coming from e.g. env vars) + * Fixed Wildfire/FirePHP/ChromePHP handling of unicode characters + * Fixed PHP 8 issues in SyslogUdpHandler + * Fixed internal type error when mbstring is missing + +### 2.1.1 (2020-07-23) + + * Fixed removing of json encoding options + * Fixed type hint of $level not accepting strings in SendGridHandler and OverflowHandler + * Fixed SwiftMailerHandler not accepting email templates with an empty subject + * Fixed array access on null in RavenHandler + * Fixed unique_id in WebProcessor not being disableable + +### 2.1.0 (2020-05-22) + + * Added `JSON_INVALID_UTF8_SUBSTITUTE` to default json flags, so that invalid UTF8 characters now get converted to [�](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character) instead of being converted from ISO-8859-15 to UTF8 as it was before, which was hardly a comprehensive solution + * Added `$ignoreEmptyContextAndExtra` option to JsonFormatter to skip empty context/extra entirely from the output + * Added `$parseMode`, `$disableWebPagePreview` and `$disableNotification` options to TelegramBotHandler + * Added tentative support for PHP 8 + * NormalizerFormatter::addJsonEncodeOption and removeJsonEncodeOption are now public to allow modifying default json flags + * Fixed GitProcessor type error when there is no git repo present + * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" + * Fixed support for relative paths in RotatingFileHandler + +### 2.0.2 (2019-12-20) + + * Fixed ElasticsearchHandler swallowing exceptions details when failing to index log records + * Fixed normalization of SoapFault objects containing non-strings as "detail" in LineFormatter + * Fixed formatting of resources in JsonFormatter + * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) + * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it + * Fixed Turkish locale messing up the conversion of level names to their constant values + +### 2.0.1 (2019-11-13) + + * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable + * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler, OverflowHandler and SamplingHandler + * Fixed BrowserConsoleHandler formatting when using multiple styles + * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings + * Fixed normalization of SoapFault objects containing non-strings as "detail" + * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding + * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). + * Fixed type error in BrowserConsoleHandler when the context array of log records was not associative. + +### 2.0.0 (2019-08-30) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: Logger methods log/debug/info/notice/warning/error/critical/alert/emergency now have explicit void return types + * Added FallbackGroupHandler which works like the WhatFailureGroupHandler but stops dispatching log records as soon as one handler accepted it + * Fixed support for UTF-8 when cutting strings to avoid cutting a multibyte-character in half + * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases + * Fixed date timezone handling in SyslogUdpHandler + +### 2.0.0-beta2 (2019-07-06) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: PHP 7.2 is now the minimum required PHP version. + * BC Break: Removed SlackbotHandler, RavenHandler and HipChatHandler, see [UPGRADE.md](UPGRADE.md) for details + * Added OverflowHandler which will only flush log records to its nested handler when reaching a certain amount of logs (i.e. only pass through when things go really bad) + * Added TelegramBotHandler to log records to a [Telegram](https://core.telegram.org/bots/api) bot account + * Added support for JsonSerializable when normalizing exceptions + * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler + * Added SoapFault details to formatted exceptions + * Fixed DeduplicationHandler silently failing to start when file could not be opened + * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records + * Fixed GelfFormatter losing some data when one attachment was too long + * Fixed issue in SignalHandler restarting syscalls functionality + * Improved performance of LogglyHandler when sending multiple logs in a single request + +### 2.0.0-beta1 (2018-12-08) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: PHP 7.1 is now the minimum required PHP version. + * BC Break: Quite a few interface changes, only relevant if you implemented your own handlers/processors/formatters + * BC Break: Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn` + * BC Break: The record timezone is now set per Logger instance and not statically anymore + * BC Break: There is no more default handler configured on empty Logger instances + * BC Break: ElasticSearchHandler renamed to ElasticaHandler + * BC Break: Various handler-specific breaks, see [UPGRADE.md](UPGRADE.md) for details + * Added scalar type hints and return hints in all the places it was possible. Switched strict_types on for more reliability. + * Added DateTimeImmutable support, all record datetime are now immutable, and will toString/json serialize with the correct date format, including microseconds (unless disabled) + * Added timezone and microseconds to the default date format + * Added SendGridHandler to use the SendGrid API to send emails + * Added LogmaticHandler to use the Logmatic.io API to store log records + * Added SqsHandler to send log records to an AWS SQS queue + * Added ElasticsearchHandler to send records via the official ES library. Elastica users should now use ElasticaHandler instead of ElasticSearchHandler + * Added NoopHandler which is similar to the NullHandle but does not prevent the bubbling of log records to handlers further down the configuration, useful for temporarily disabling a handler in configuration files + * Added ProcessHandler to write log output to the STDIN of a given process + * Added HostnameProcessor that adds the machine's hostname to log records + * Added a `$dateFormat` option to the PsrLogMessageProcessor which lets you format DateTime instances nicely + * Added support for the PHP 7.x `mongodb` extension in the MongoDBHandler + * Fixed many minor issues in various handlers, and probably added a few regressions too ### 1.26.1 (2021-05-28) @@ -67,7 +283,7 @@ * Added a way to log signals being received using Monolog\SignalHandler * Added ability to customize error handling at the Logger level using Logger::setExceptionHandler * Added InsightOpsHandler to migrate users of the LogEntriesHandler - * Added protection to NormalizerHandler against circular and very deep structures, it now stops normalizing at a depth of 9 + * Added protection to NormalizerFormatter against circular and very deep structures, it now stops normalizing at a depth of 9 * Added capture of stack traces to ErrorHandler when logging PHP errors * Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts * Added forwarding of context info to FluentdFormatter @@ -107,7 +323,7 @@ * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily * Added MercurialProcessor to add mercurial revision and branch names to log records * Added support for AWS SDK v3 in DynamoDbHandler - * Fixed fatal errors occuring when normalizing generators that have been fully consumed + * Fixed fatal errors occurring when normalizing generators that have been fully consumed * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore * Fixed SyslogUdpHandler to avoid sending empty frames @@ -117,7 +333,7 @@ * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order - * Added ability to format the main line of text the SlackHandler sends by explictly setting a formatter on the handler + * Added ability to format the main line of text the SlackHandler sends by explicitly setting a formatter on the handler * Added information about SoapFault instances in NormalizerFormatter * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level @@ -239,7 +455,7 @@ * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data * Added $host to HipChatHandler for users of private instances * Added $transactionName to NewRelicHandler and support for a transaction_name context value - * Fixed MandrillHandler to avoid outputing API call responses + * Fixed MandrillHandler to avoid outputting API call responses * Fixed some non-standard behaviors in SyslogUdpHandler ### 1.11.0 (2014-09-30) diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE index 16473219b..aa2a0426c 100644 --- a/vendor/monolog/monolog/LICENSE +++ b/vendor/monolog/monolog/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011-2016 Jordi Boggiano +Copyright (c) 2011-2020 Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/monolog/monolog/README.md b/vendor/monolog/monolog/README.md index a578eb228..bfcae0c00 100644 --- a/vendor/monolog/monolog/README.md +++ b/vendor/monolog/monolog/README.md @@ -1,4 +1,4 @@ -# Monolog - Logging for PHP [![Build Status](https://img.shields.io/travis/Seldaek/monolog.svg)](https://travis-ci.org/Seldaek/monolog) +# Monolog - Logging for PHP [![Continuous Integration](https://github.com/Seldaek/monolog/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/Seldaek/monolog/actions) [![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) [![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) @@ -36,16 +36,23 @@ $log = new Logger('name'); $log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); // add records to the log -$log->addWarning('Foo'); -$log->addError('Bar'); +$log->warning('Foo'); +$log->error('Bar'); ``` ## Documentation - [Usage Instructions](doc/01-usage.md) - [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) -- [Utility classes](doc/03-utilities.md) +- [Utility Classes](doc/03-utilities.md) - [Extending Monolog](doc/04-extending.md) +- [Log Record Structure](doc/message-structure.md) + +## Support Monolog Financially + +Get supported Monolog and help fund the project with the [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-monolog-monolog?utm_source=packagist-monolog-monolog&utm_medium=referral&utm_campaign=enterprise) or via [GitHub sponsorship](https://github.com/sponsors/Seldaek). + +Tidelift delivers commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. ## Third Party Packages @@ -57,7 +64,11 @@ can also add your own there if you publish one. ### Requirements -- Monolog works with PHP 5.3 or above, and is also tested to work with HHVM. +- Monolog `^2.0` works with PHP 7.2 or above, use Monolog `^1.25` for PHP 5.3+ support. + +### Support + +Monolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 where possible to benefit from all the latest features and fixes. ### Submitting bugs and feature requests @@ -67,26 +78,33 @@ Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/mono - Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) can be used very easily with Monolog since it implements the interface. -- [Symfony2](http://symfony.com) comes out of the box with Monolog. -- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog. -- [Laravel 4 & 5](http://laravel.com/) come out of the box with Monolog. +- [Symfony](http://symfony.com) comes out of the box with Monolog. +- [Laravel](http://laravel.com/) comes out of the box with Monolog. - [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. -- [PPI](http://www.ppi.io/) comes out of the box with Monolog. +- [PPI](https://github.com/ppi/framework) comes out of the box with Monolog. - [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. - [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. - [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. - [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. -- [Nette Framework](http://nette.org/en/) can be used with Monolog via [Kdyby/Monolog](https://github.com/Kdyby/Monolog) extension. +- [Nette Framework](http://nette.org/en/) is usable with Monolog via the [contributte/monolog](https://github.com/contributte/monolog) or [orisai/nette-monolog](https://github.com/orisai/nette-monolog) extensions. - [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. +- [FuelPHP](http://fuelphp.com/) comes out of the box with Monolog. +- [Equip Framework](https://github.com/equip/framework) comes out of the box with Monolog. +- [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) or [yii2-psr-log-target](https://github.com/samdark/yii2-psr-log-target) plugins. +- [Hawkbit Micro Framework](https://github.com/HawkBitPhp/hawkbit) comes out of the box with Monolog. +- [SilverStripe 4](https://www.silverstripe.org/) comes out of the box with Monolog. +- [Drupal](https://www.drupal.org/) is usable with Monolog via the [monolog](https://www.drupal.org/project/monolog) module. +- [Aimeos ecommerce framework](https://aimeos.org/) is usable with Monolog via the [ai-monolog](https://github.com/aimeos/ai-monolog) extension. +- [Magento](https://magento.com/) comes out of the box with Monolog. ### Author Jordi Boggiano - -
-See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project. +See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) who participated in this project. ### License -Monolog is licensed under the MIT License - see the `LICENSE` file for details +Monolog is licensed under the MIT License - see the [LICENSE](LICENSE) file for details ### Acknowledgements diff --git a/vendor/monolog/monolog/UPGRADE.md b/vendor/monolog/monolog/UPGRADE.md new file mode 100644 index 000000000..84e15e6b7 --- /dev/null +++ b/vendor/monolog/monolog/UPGRADE.md @@ -0,0 +1,72 @@ +### 2.0.0 + +- `Monolog\Logger::API` can be used to distinguish between a Monolog `1` and `2` + install of Monolog when writing integration code. + +- Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) + methods as well as `emerg`, `crit`, `err` and `warn`. + +- DateTime are now formatted with a timezone and microseconds (unless disabled). + Various formatters and log output might be affected, which may mess with log parsing + in some cases. + +- The `datetime` in every record array is now a DateTimeImmutable, not that you + should have been modifying these anyway. + +- The timezone is now set per Logger instance and not statically, either + via ->setTimezone or passed in the constructor. Calls to Logger::setTimezone + should be converted. + +- `HandlerInterface` has been split off and two new interfaces now exist for + more granular controls: `ProcessableHandlerInterface` and + `FormattableHandlerInterface`. Handlers not extending `AbstractHandler` + should make sure to implement the relevant interfaces. + +- `HandlerInterface` now requires the `close` method to be implemented. This + only impacts you if you implement the interface yourself, but you can extend + the new `Monolog\Handler\Handler` base class too. + +- There is no more default handler configured on empty Logger instances, if + you were relying on that you will not get any output anymore, make sure to + configure the handler you need. + +#### LogglyFormatter + +- The records' `datetime` is not sent anymore. Only `timestamp` is sent to Loggly. + +#### AmqpHandler + +- Log levels are not shortened to 4 characters anymore. e.g. a warning record + will be sent using the `warning.channel` routing key instead of `warn.channel` + as in 1.x. +- The exchange name does not default to 'log' anymore, and it is completely ignored + now for the AMQP extension users. Only PHPAmqpLib uses it if provided. + +#### RotatingFileHandler + +- The file name format must now contain `{date}` and the date format must be set + to one of the predefined FILE_PER_* constants to avoid issues with file rotation. + See `setFilenameFormat`. + +#### LogstashFormatter + +- Removed Logstash V0 support +- Context/extra prefix has been removed in favor of letting users configure the exact key being sent +- Context/extra data are now sent as an object instead of single keys + +#### HipChatHandler + +- Removed deprecated HipChat handler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead + +#### SlackbotHandler + +- Removed deprecated SlackbotHandler handler, use SlackWebhookHandler or SlackHandler instead + +#### RavenHandler + +- Removed deprecated RavenHandler handler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead + +#### ElasticSearchHandler + +- As support for the official Elasticsearch library was added, the former ElasticSearchHandler has been + renamed to ElasticaHandler and the new one added as ElasticsearchHandler. diff --git a/vendor/monolog/monolog/composer.json b/vendor/monolog/monolog/composer.json index aecc40f39..d96c94c3e 100644 --- a/vendor/monolog/monolog/composer.json +++ b/vendor/monolog/monolog/composer.json @@ -2,44 +2,55 @@ "name": "monolog/monolog", "description": "Sends your logs to files, sockets, inboxes, databases and various web services", "keywords": ["log", "logging", "psr-3"], - "homepage": "http://github.com/Seldaek/monolog", + "homepage": "https://github.com/Seldaek/monolog", "type": "library", "license": "MIT", "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { - "phpunit/phpunit": "~4.5", - "graylog2/gelf-php": "~1.0", - "sentry/sentry": "^0.13", - "ruflin/elastica": ">=0.90 <3.0", - "doctrine/couchdb": "~1.0@dev", + "ext-json": "*", "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "php-amqplib/php-amqplib": "~2.4", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "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": "^7", "swiftmailer/swiftmailer": "^5.3|^6.0", - "php-console/php-console": "^3.1.3", - "phpstan/phpstan": "^0.12.59" + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" }, "suggest": { "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "sentry/sentry": "Allow sending log messages to a Sentry server", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "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", + "ext-openssl": "Required to send log messages using SSL" }, "autoload": { "psr-4": {"Monolog\\": "src/Monolog"} @@ -48,13 +59,23 @@ "psr-4": {"Monolog\\": "tests/Monolog"} }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } }, "scripts": { - "test": "vendor/bin/phpunit", - "phpstan": "vendor/bin/phpstan analyse" + "test": "@php vendor/bin/phpunit", + "phpstan": "@php vendor/bin/phpstan analyse" }, "config": { - "lock": false + "lock": false, + "sort-packages": true, + "platform-check": false, + "allow-plugins": { + "composer/package-versions-deprecated": true + } } } diff --git a/vendor/monolog/monolog/phpstan.neon.dist b/vendor/monolog/monolog/phpstan.neon.dist deleted file mode 100644 index 1fe45df73..000000000 --- a/vendor/monolog/monolog/phpstan.neon.dist +++ /dev/null @@ -1,16 +0,0 @@ -parameters: - level: 3 - - paths: - - src/ -# - tests/ - - - ignoreErrors: - - '#zend_monitor_|ZEND_MONITOR_#' - - '#RollbarNotifier#' - - '#Predis\\Client#' - - '#^Cannot call method ltrim\(\) on int\|false.$#' - - '#^Access to an undefined property Raven_Client::\$context.$#' - - '#MongoDB\\(Client|Collection)#' - - '#Gelf\\IMessagePublisher#' diff --git a/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php b/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php new file mode 100644 index 000000000..188bbb0d8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Attribute; + +/** + * A reusable attribute to help configure a class or a method as a processor. + * + * Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer. + * + * Using it with the Monolog library only has no effect at all: processors should still be turned into a callable if + * needed and manually pushed to the loggers and to the processable handlers. + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class AsMonologProcessor +{ + /** @var string|null */ + public $channel = null; + /** @var string|null */ + public $handler = null; + /** @var string|null */ + public $method = null; + + /** + * @param string|null $channel The logging channel the processor should be pushed to. + * @param string|null $handler The handler the processor should be pushed to. + * @param string|null $method The method that processes the records (if the attribute is used at the class level). + */ + public function __construct( + ?string $channel = null, + ?string $handler = null, + ?string $method = null + ) { + $this->channel = $channel; + $this->handler = $handler; + $this->method = $method; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php new file mode 100644 index 000000000..789f9bfc4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use DateTimeZone; + +/** + * Overrides default json encoding of date time objects + * + * @author Menno Holtkamp + * @author Jordi Boggiano + */ +class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable +{ + /** + * @var bool + */ + private $useMicroseconds; + + public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null) + { + $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); + } + + public function jsonSerialize(): string + { + if ($this->useMicroseconds) { + return $this->format('Y-m-d\TH:i:s.uP'); + } + + return $this->format('Y-m-d\TH:i:sP'); + } + + public function __toString(): string + { + return $this->jsonSerialize(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php index 5121c2cd0..07424903b 100644 --- a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php +++ b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php @@ -1,4 +1,4 @@ - an array of class name to LogLevel::* constant mapping */ + private $uncaughtExceptionLevelMap = []; - private $previousErrorHandler; - private $errorLevelMap; - private $handleOnlyReportedErrors; + /** @var callable|true|null */ + private $previousErrorHandler = null; + /** @var array an array of E_* constant to LogLevel::* constant mapping */ + private $errorLevelMap = []; + /** @var bool */ + private $handleOnlyReportedErrors = true; - private $hasFatalErrorHandler; - private $fatalLevel; - private $reservedMemory; - private $lastFatalTrace; - private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + /** @var bool */ + private $hasFatalErrorHandler = false; + /** @var LogLevel::* */ + private $fatalLevel = LogLevel::ALERT; + /** @var ?string */ + private $reservedMemory = null; + /** @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]; public function __construct(LoggerInterface $logger) { @@ -51,24 +61,21 @@ public function __construct(LoggerInterface $logger) * * By default it will handle errors, exceptions and fatal errors * - * @param LoggerInterface $logger - * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling - * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling - * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling + * @param LogLevel::*|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling * @return ErrorHandler */ - public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) + public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self { - //Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929 - class_exists('\\Psr\\Log\\LogLevel', true); - /** @phpstan-ignore-next-line */ $handler = new static($logger); if ($errorLevelMap !== false) { $handler->registerErrorHandler($errorLevelMap); } - if ($exceptionLevel !== false) { - $handler->registerExceptionHandler($exceptionLevel); + if ($exceptionLevelMap !== false) { + $handler->registerExceptionHandler($exceptionLevelMap); } if ($fatalLevel !== false) { $handler->registerFatalHandler($fatalLevel); @@ -77,38 +84,79 @@ class_exists('\\Psr\\Log\\LogLevel', true); return $handler; } - public function registerExceptionHandler($level = null, $callPrevious = true) + /** + * @param array $levelMap an array of class name to LogLevel::* constant mapping + * @return $this + */ + public function registerExceptionHandler(array $levelMap = [], bool $callPrevious = true): self { - $prev = set_exception_handler(array($this, 'handleException')); - $this->uncaughtExceptionLevel = $level; + $prev = set_exception_handler(function (\Throwable $e): void { + $this->handleException($e); + }); + $this->uncaughtExceptionLevelMap = $levelMap; + foreach ($this->defaultExceptionLevelMap() as $class => $level) { + if (!isset($this->uncaughtExceptionLevelMap[$class])) { + $this->uncaughtExceptionLevelMap[$class] = $level; + } + } if ($callPrevious && $prev) { $this->previousExceptionHandler = $prev; } + + return $this; } - public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true) + /** + * @param array $levelMap an array of E_* constant to LogLevel::* constant mapping + * @return $this + */ + public function registerErrorHandler(array $levelMap = [], bool $callPrevious = true, int $errorTypes = -1, bool $handleOnlyReportedErrors = true): self { - $prev = set_error_handler(array($this, 'handleError'), $errorTypes); + $prev = set_error_handler([$this, 'handleError'], $errorTypes); $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); if ($callPrevious) { $this->previousErrorHandler = $prev ?: true; + } else { + $this->previousErrorHandler = null; } $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + + return $this; } - public function registerFatalHandler($level = null, $reservedMemorySize = 20) + /** + * @param LogLevel::*|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT + * @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done + */ + public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self { - register_shutdown_function(array($this, 'handleFatalError')); + register_shutdown_function([$this, 'handleFatalError']); $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); - $this->fatalLevel = $level; + $this->fatalLevel = null === $level ? LogLevel::ALERT : $level; $this->hasFatalErrorHandler = true; + + return $this; } - protected function defaultErrorLevelMap() + /** + * @return array + */ + protected function defaultExceptionLevelMap(): array { - return array( + return [ + 'ParseError' => LogLevel::CRITICAL, + 'Throwable' => LogLevel::ERROR, + ]; + } + + /** + * @return array + */ + protected function defaultErrorLevelMap(): array + { + return [ E_ERROR => LogLevel::CRITICAL, E_WARNING => LogLevel::WARNING, E_PARSE => LogLevel::ALERT, @@ -120,26 +168,38 @@ protected function defaultErrorLevelMap() E_USER_ERROR => LogLevel::ERROR, E_USER_WARNING => LogLevel::WARNING, E_USER_NOTICE => LogLevel::NOTICE, - E_STRICT => LogLevel::NOTICE, + 2048 => LogLevel::NOTICE, // E_STRICT E_RECOVERABLE_ERROR => LogLevel::ERROR, E_DEPRECATED => LogLevel::NOTICE, E_USER_DEPRECATED => LogLevel::NOTICE, - ); + ]; } /** - * @private + * @phpstan-return never */ - public function handleException($e) + private function handleException(\Throwable $e): void { + $level = LogLevel::ERROR; + foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) { + if ($e instanceof $class) { + $level = $candidate; + break; + } + } + $this->logger->log( - $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel, + $level, sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), - array('exception' => $e) + ['exception' => $e] ); if ($this->previousExceptionHandler) { - call_user_func($this->previousExceptionHandler, $e); + ($this->previousExceptionHandler)($e); + } + + if (!headers_sent() && in_array(strtolower((string) ini_get('display_errors')), ['0', '', 'false', 'off', 'none', 'no'], true)) { + http_response_code(500); } exit(255); @@ -147,59 +207,67 @@ public function handleException($e) /** * @private + * + * @param mixed[] $context */ - public function handleError($code, $message, $file = '', $line = 0, $context = array()) + public function handleError(int $code, string $message, string $file = '', int $line = 0, ?array $context = []): bool { if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { - return; + return false; } // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { - $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; - $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line)); + $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]); } else { - // http://php.net/manual/en/function.debug-backtrace.php - // As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. - // Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. - $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + $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) { return false; } elseif ($this->previousErrorHandler) { - return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); + return (bool) ($this->previousErrorHandler)($code, $message, $file, $line, $context); } + + return true; } /** * @private */ - public function handleFatalError() + public function handleFatalError(): void { - $this->reservedMemory = null; + $this->reservedMemory = ''; + + if (is_array($this->lastFatalData)) { + $lastError = $this->lastFatalData; + } else { + $lastError = error_get_last(); + } - $lastError = error_get_last(); if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { + $trace = $lastError['trace'] ?? null; $this->logger->log( - $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel, + $this->fatalLevel, 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], - array('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) { foreach ($this->logger->getHandlers() as $handler) { - if ($handler instanceof AbstractHandler) { - $handler->close(); - } + $handler->close(); } } } } - private static function codeToString($code) + /** + * @param int $code + */ + private static function codeToString($code): string { switch ($code) { case E_ERROR: @@ -224,7 +292,7 @@ private static function codeToString($code) return 'E_USER_WARNING'; case E_USER_NOTICE: return 'E_USER_NOTICE'; - case E_STRICT: + case 2048: return 'E_STRICT'; case E_RECOVERABLE_ERROR: return 'E_RECOVERABLE_ERROR'; diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php index 9beda1e76..aa1884b9c 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -1,4 +1,4 @@ - */ - private $logLevels = array( + private $logLevels = [ Logger::DEBUG => 'log', Logger::INFO => 'info', Logger::NOTICE => 'info', @@ -32,10 +34,10 @@ class ChromePHPFormatter implements FormatterInterface Logger::CRITICAL => 'error', Logger::ALERT => 'error', Logger::EMERGENCY => 'error', - ); + ]; /** - * {@inheritdoc} + * {@inheritDoc} */ public function format(array $record) { @@ -46,7 +48,7 @@ public function format(array $record) unset($record['extra']['file'], $record['extra']['line']); } - $message = array('message' => $record['message']); + $message = ['message' => $record['message']]; if ($record['context']) { $message['context'] = $record['context']; } @@ -57,17 +59,20 @@ public function format(array $record) $message = reset($message); } - return array( + return [ $record['channel'], $message, $backtrace, $this->logLevels[$record['level']], - ); + ]; } + /** + * {@inheritDoc} + */ public function formatBatch(array $records) { - $formatted = array(); + $formatted = []; foreach ($records as $record) { $formatted[] = $this->format($record); diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php index 4c556cf17..6c8a9ab5e 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ class ElasticaFormatter extends NormalizerFormatter { @@ -26,15 +28,15 @@ class ElasticaFormatter extends NormalizerFormatter protected $index; /** - * @var string Elastic search document type + * @var ?string Elastic search document type */ protected $type; /** - * @param string $index Elastic Search index name - * @param string $type Elastic Search document type + * @param string $index Elastic Search index name + * @param ?string $type Elastic Search document type, deprecated as of Elastica 7 */ - public function __construct($index, $type) + public function __construct(string $index, ?string $type) { // elasticsearch requires a ISO 8601 format date with optional millisecond precision. parent::__construct('Y-m-d\TH:i:s.uP'); @@ -44,7 +46,7 @@ public function __construct($index, $type) } /** - * {@inheritdoc} + * {@inheritDoc} */ public function format(array $record) { @@ -53,35 +55,33 @@ public function format(array $record) return $this->getDocument($record); } - /** - * Getter index - * @return string - */ - public function getIndex() + public function getIndex(): string { return $this->index; } /** - * Getter type - * @return string + * @deprecated since Elastica 7 type has no effect */ - public function getType() + public function getType(): string { + /** @phpstan-ignore-next-line */ return $this->type; } /** * Convert a log message into an Elastica Document * - * @param array $record Log message - * @return Document + * @phpstan-param Record $record */ - protected function getDocument($record) + protected function getDocument(array $record): Document { $document = new Document(); $document->setData($record); - $document->setType($this->type); + if (method_exists($document, 'setType')) { + /** @phpstan-ignore-next-line */ + $document->setType($this->type); + } $document->setIndex($this->index); return $document; diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php new file mode 100644 index 000000000..b792b819c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use DateTimeInterface; + +/** + * Format a log message into an Elasticsearch record + * + * @author Avtandil Kikabidze + */ +class ElasticsearchFormatter extends NormalizerFormatter +{ + /** + * @var string Elasticsearch index name + */ + protected $index; + + /** + * @var string Elasticsearch record type + */ + protected $type; + + /** + * @param string $index Elasticsearch index name + * @param string $type Elasticsearch record type + */ + public function __construct(string $index, string $type) + { + // Elasticsearch requires an ISO 8601 format date with optional millisecond precision. + parent::__construct(DateTimeInterface::ISO8601); + + $this->index = $index; + $this->type = $type; + } + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + /** + * Getter index + * + * @return string + */ + public function getIndex(): string + { + return $this->index; + } + + /** + * Getter type + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Convert a log message into an Elasticsearch record + * + * @param mixed[] $record Log message + * @return mixed[] + */ + protected function getDocument(array $record): array + { + $record['_index'] = $this->index; + $record['_type'] = $this->type; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php index 5094af3c7..867ae586b 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php @@ -1,4 +1,4 @@ - + * @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 { @@ -28,26 +29,24 @@ class FlowdockFormatter implements FormatterInterface */ private $sourceEmail; - /** - * @param string $source - * @param string $sourceEmail - */ - public function __construct($source, $sourceEmail) + public function __construct(string $source, string $sourceEmail) { $this->source = $source; $this->sourceEmail = $sourceEmail; } /** - * {@inheritdoc} + * {@inheritDoc} + * + * @return mixed[] */ - public function format(array $record) + public function format(array $record): array { - $tags = array( + $tags = [ '#logs', '#' . strtolower($record['level_name']), '#' . $record['channel'], - ); + ]; foreach ($record['extra'] as $value) { $tags[] = '#' . $value; @@ -60,24 +59,26 @@ public function format(array $record) $this->getShortMessage($record['message']) ); - $record['flowdock'] = array( + $record['flowdock'] = [ 'source' => $this->source, 'from_address' => $this->sourceEmail, 'subject' => $subject, 'content' => $record['message'], 'tags' => $tags, 'project' => $this->source, - ); + ]; return $record; } /** - * {@inheritdoc} + * {@inheritDoc} + * + * @return mixed[][] */ - public function formatBatch(array $records) + public function formatBatch(array $records): array { - $formatted = array(); + $formatted = []; foreach ($records as $record) { $formatted[] = $this->format($record); @@ -86,12 +87,7 @@ public function formatBatch(array $records) return $formatted; } - /** - * @param string $message - * - * @return string - */ - public function getShortMessage($message) + public function getShortMessage(string $message): string { static $hasMbString; diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php index f8ead4756..29b14d30d 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php @@ -1,4 +1,4 @@ -levelTag = (bool) $levelTag; + $this->levelTag = $levelTag; } - public function isUsingLevelsInTag() + public function isUsingLevelsInTag(): bool { return $this->levelTag; } - public function format(array $record) + public function format(array $record): string { $tag = $record['channel']; if ($this->levelTag) { $tag .= '.' . strtolower($record['level_name']); } - $message = array( + $message = [ 'message' => $record['message'], 'context' => $record['context'], 'extra' => $record['extra'], - ); + ]; if (!$this->levelTag) { $message['level'] = $record['level']; $message['level_name'] = $record['level_name']; } - return Utils::jsonEncode(array($tag, $record['datetime']->getTimestamp(), $message)); + return Utils::jsonEncode([$tag, $record['datetime']->getTimestamp(), $message]); } - public function formatBatch(array $records) + public function formatBatch(array $records): string { $message = ''; foreach ($records as $record) { diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php index b5de75111..19617ec5f 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ interface FormatterInterface { @@ -23,6 +25,8 @@ interface FormatterInterface * * @param array $record A record to format * @return mixed The formatted record + * + * @phpstan-param Record $record */ public function format(array $record); @@ -31,6 +35,8 @@ public function format(array $record); * * @param array $records A set of records to format * @return mixed The formatted set of records + * + * @phpstan-param Record[] $records */ public function formatBatch(array $records); } diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php index 2c1b0e86c..3b3e1e7f6 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger */ class GelfMessageFormatter extends NormalizerFormatter { - const DEFAULT_MAX_LENGTH = 32766; + protected const DEFAULT_MAX_LENGTH = 32766; /** * @var string the name of the system for the Gelf log message @@ -44,10 +47,19 @@ class GelfMessageFormatter extends NormalizerFormatter */ protected $maxLength; + /** + * @var int + */ + private $gelfVersion = 2; + /** * Translates Monolog log levels to Graylog2 log priorities. + * + * @var array + * + * @phpstan-var array */ - private $logLevels = array( + private $logLevels = [ Logger::DEBUG => 7, Logger::INFO => 6, Logger::NOTICE => 5, @@ -56,25 +68,41 @@ class GelfMessageFormatter extends NormalizerFormatter Logger::CRITICAL => 2, Logger::ALERT => 1, Logger::EMERGENCY => 0, - ); + ]; - public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $maxLength = null) + public function __construct(?string $systemName = null, ?string $extraPrefix = null, string $contextPrefix = 'ctxt_', ?int $maxLength = null) { + if (!class_exists(Message::class)) { + throw new \RuntimeException('Composer package graylog2/gelf-php is required to use Monolog\'s GelfMessageFormatter'); + } + parent::__construct('U.u'); - $this->systemName = $systemName ?: gethostname(); + $this->systemName = (is_null($systemName) || $systemName === '') ? (string) gethostname() : $systemName; - $this->extraPrefix = $extraPrefix; + $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; + } } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function format(array $record) + public function format(array $record): Message { - $record = parent::format($record); + $context = $extra = []; + if (isset($record['context'])) { + /** @var mixed[] $context */ + $context = parent::normalize($record['context']); + } + if (isset($record['extra'])) { + /** @var mixed[] $extra */ + $extra = parent::normalize($record['extra']); + } if (!isset($record['datetime'], $record['message'], $record['level'])) { throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given'); @@ -87,49 +115,58 @@ public function format(array $record) ->setHost($this->systemName) ->setLevel($this->logLevels[$record['level']]); - // message length + system name length + 200 for padding / metadata + // message length + system name length + 200 for padding / metadata $len = 200 + strlen((string) $record['message']) + strlen($this->systemName); if ($len > $this->maxLength) { - $message->setShortMessage(substr($record['message'], 0, $this->maxLength)); + $message->setShortMessage(Utils::substr($record['message'], 0, $this->maxLength)); } - if (isset($record['channel'])) { - $message->setFacility($record['channel']); - } - if (isset($record['extra']['line'])) { - $message->setLine($record['extra']['line']); - unset($record['extra']['line']); - } - if (isset($record['extra']['file'])) { - $message->setFile($record['extra']['file']); - unset($record['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 ($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, substr($val, 0, $this->maxLength)); - break; + $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, substr($val, 0, $this->maxLength)); - break; + $message->setAdditional($this->contextPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); + + continue; } $message->setAdditional($this->contextPrefix . $key, $val); } - if (null === $message->getFile() && isset($record['context']['exception']['file'])) { - if (preg_match("/^(.+):([0-9]+)$/", $record['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]); + } } } diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php new file mode 100644 index 000000000..ca52ebf4e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use DateTimeInterface; +use Monolog\LogRecord; + +/** + * Encodes message information into JSON in a format compatible with Cloud logging. + * + * @see https://cloud.google.com/logging/docs/structured-logging + * @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry + * + * @author Luís Cobucci + */ +final class GoogleCloudLoggingFormatter extends JsonFormatter +{ + /** {@inheritdoc} **/ + public function format(array $record): string + { + // Re-key level for GCP logging + $record['severity'] = $record['level_name']; + $record['time'] = $record['datetime']->format(DateTimeInterface::RFC3339_EXTENDED); + + // Remove keys that are not used by GCP + unset($record['level'], $record['level_name'], $record['datetime']); + + return parent::format($record); + } +} + diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php index 9e8d2d018..10a4311cb 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php @@ -1,4 +1,5 @@ - */ - protected $logLevels = array( - Logger::DEBUG => '#cccccc', - Logger::INFO => '#468847', - Logger::NOTICE => '#3a87ad', - Logger::WARNING => '#c09853', - Logger::ERROR => '#f0ad4e', - Logger::CRITICAL => '#FF7708', - Logger::ALERT => '#C12A19', + protected $logLevels = [ + Logger::DEBUG => '#CCCCCC', + Logger::INFO => '#28A745', + Logger::NOTICE => '#17A2B8', + Logger::WARNING => '#FFC107', + Logger::ERROR => '#FD7E14', + Logger::CRITICAL => '#DC3545', + Logger::ALERT => '#821722', Logger::EMERGENCY => '#000000', - ); + ]; /** - * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format */ - public function __construct($dateFormat = null) + public function __construct(?string $dateFormat = null) { parent::__construct($dateFormat); } @@ -47,12 +50,11 @@ public function __construct($dateFormat = null) /** * Creates an HTML table row * - * @param string $th Row header content - * @param string $td Row standard cell content - * @param bool $escapeTd false if td content must not be html escaped - * @return string + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped */ - protected function addRow($th, $td = ' ', $escapeTd = true) + protected function addRow(string $th, string $td = ' ', bool $escapeTd = true): string { $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); if ($escapeTd) { @@ -69,7 +71,7 @@ protected function addRow($th, $td = ' ', $escapeTd = true) * @param int $level Error level * @return string */ - protected function addTitle($title, $level) + protected function addTitle(string $title, int $level): string { $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); @@ -79,21 +81,20 @@ protected function addTitle($title, $level) /** * Formats a log record. * - * @param array $record A record to format - * @return mixed The formatted record + * @return string The formatted record */ - public function format(array $record) + public function format(array $record): string { $output = $this->addTitle($record['level_name'], $record['level']); $output .= ''; $output .= $this->addRow('Message', (string) $record['message']); - $output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat)); + $output .= $this->addRow('Time', $this->formatDate($record['datetime'])); $output .= $this->addRow('Channel', $record['channel']); if ($record['context']) { $embeddedTable = '
'; foreach ($record['context'] as $key => $value) { - $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); } $embeddedTable .= '
'; $output .= $this->addRow('Context', $embeddedTable, false); @@ -101,7 +102,7 @@ public function format(array $record) if ($record['extra']) { $embeddedTable = ''; foreach ($record['extra'] as $key => $value) { - $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); } $embeddedTable .= '
'; $output .= $this->addRow('Extra', $embeddedTable, false); @@ -113,10 +114,9 @@ public function format(array $record) /** * Formats a set of log records. * - * @param array $records A set of records to format - * @return mixed The formatted set of records + * @return string The formatted set of records */ - public function formatBatch(array $records) + public function formatBatch(array $records): string { $message = ''; foreach ($records as $record) { @@ -126,17 +126,17 @@ public function formatBatch(array $records) return $message; } - protected function convertToString($data) + /** + * @param mixed $data + */ + protected function convertToString($data): string { if (null === $data || is_scalar($data)) { return (string) $data; } $data = $this->normalize($data); - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - return Utils::jsonEncode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, true); - } - return str_replace('\\/', '/', Utils::jsonEncode($data, null, true)); + return Utils::jsonEncode($data, JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, true); } } diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php index 4c5422d56..753e6852c 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ class JsonFormatter extends NormalizerFormatter { - const BATCH_MODE_JSON = 1; - const BATCH_MODE_NEWLINES = 2; + public const BATCH_MODE_JSON = 1; + public const BATCH_MODE_NEWLINES = 2; + /** @var self::BATCH_MODE_* */ protected $batchMode; + /** @var bool */ protected $appendNewline; - - /** - * @var bool - */ + /** @var bool */ + protected $ignoreEmptyContextAndExtra; + /** @var bool */ protected $includeStacktraces = false; /** - * @param int $batchMode - * @param bool $appendNewline - * @param int $maxDepth + * @param self::BATCH_MODE_* $batchMode */ - public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true, $maxDepth = 9) + public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false) { - parent::__construct(null, $maxDepth); $this->batchMode = $batchMode; $this->appendNewline = $appendNewline; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + $this->includeStacktraces = $includeStacktraces; + + parent::__construct(); } /** @@ -53,36 +55,49 @@ public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = * formatted as a JSON-encoded array. However, for * compatibility with some API endpoints, alternative styles * are available. - * - * @return int */ - public function getBatchMode() + public function getBatchMode(): int { return $this->batchMode; } /** * True if newlines are appended to every formatted record - * - * @return bool */ - public function isAppendingNewlines() + public function isAppendingNewlines(): bool { return $this->appendNewline; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function format(array $record) + public function format(array $record): string { - return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : ''); + $normalized = $this->normalize($record); + + if (isset($normalized['context']) && $normalized['context'] === []) { + if ($this->ignoreEmptyContextAndExtra) { + unset($normalized['context']); + } else { + $normalized['context'] = new \stdClass; + } + } + if (isset($normalized['extra']) && $normalized['extra'] === []) { + if ($this->ignoreEmptyContextAndExtra) { + unset($normalized['extra']); + } else { + $normalized['extra'] = new \stdClass; + } + } + + return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : ''); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function formatBatch(array $records) + public function formatBatch(array $records): string { switch ($this->batchMode) { case static::BATCH_MODE_NEWLINES: @@ -95,20 +110,21 @@ public function formatBatch(array $records) } /** - * @param bool $include + * @return self */ - public function includeStacktraces($include = true) + public function includeStacktraces(bool $include = true): self { $this->includeStacktraces = $include; + + return $this; } /** * Return a JSON-encoded array of records. * - * @param array $records - * @return string + * @phpstan-param Record[] $records */ - protected function formatBatchJson(array $records) + protected function formatBatchJson(array $records): string { return $this->toJson($this->normalize($records), true); } @@ -117,10 +133,9 @@ protected function formatBatchJson(array $records) * Use new lines to separate records instead of a * JSON-encoded array. * - * @param array $records - * @return string + * @phpstan-param Record[] $records */ - protected function formatBatchNewlines(array $records) + protected function formatBatchNewlines(array $records): string { $instance = $this; @@ -141,30 +156,51 @@ protected function formatBatchNewlines(array $records) * * @return mixed */ - protected function normalize($data, $depth = 0) + protected function normalize($data, int $depth = 0) { - if ($depth > $this->maxDepth) { - return 'Over '.$this->maxDepth.' levels deep, aborting normalization'; + if ($depth > $this->maxNormalizeDepth) { + return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization'; } if (is_array($data)) { - $normalized = array(); + $normalized = []; $count = 1; foreach ($data as $key => $value) { - if ($count++ > 1000) { - $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; + if ($count++ > $this->maxNormalizeItemCount) { + $normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.count($data).' total), aborting normalization'; break; } - $normalized[$key] = $this->normalize($value, $depth+1); + $normalized[$key] = $this->normalize($value, $depth + 1); } return $normalized; } - if ($data instanceof Exception || $data instanceof Throwable) { - return $this->normalizeException($data); + if (is_object($data)) { + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } + + 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 (\get_class($data) === '__PHP_Incomplete_Class') { + return new \ArrayObject($data); + } + + if (method_exists($data, '__toString')) { + return $data->__toString(); + } + + return $data; } if (is_resource($data)) { @@ -178,35 +214,13 @@ protected function normalize($data, $depth = 0) * Normalizes given exception with or without its own stack trace based on * `includeStacktraces` property. * - * @param Exception|Throwable $e - * - * @return array + * {@inheritDoc} */ - protected function normalizeException($e) + protected function normalizeException(Throwable $e, int $depth = 0): array { - // TODO 2.0 only check for Throwable - if (!$e instanceof Exception && !$e instanceof Throwable) { - throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); - } - - $data = array( - 'class' => Utils::getClass($e), - 'message' => $e->getMessage(), - 'code' => (int) $e->getCode(), - 'file' => $e->getFile().':'.$e->getLine(), - ); - - if ($this->includeStacktraces) { - $trace = $e->getTrace(); - foreach ($trace as $frame) { - if (isset($frame['file'])) { - $data['trace'][] = $frame['file'].':'.$frame['line']; - } - } - } - - if ($previous = $e->getPrevious()) { - $data['previous'] = $this->normalizeException($previous); + $data = parent::normalizeException($e, $depth); + if (!$this->includeStacktraces) { + unset($data['trace']); } return $data; diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php index acc1fd38f..e6e789833 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -1,4 +1,4 @@ -format = $format ?: static::SIMPLE_FORMAT; + $this->format = $format === null ? static::SIMPLE_FORMAT : $format; $this->allowInlineLineBreaks = $allowInlineLineBreaks; $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + $this->includeStacktraces($includeStacktraces); parent::__construct($dateFormat); } - public function includeStacktraces($include = true) + public function includeStacktraces(bool $include = true, ?callable $parser = null): self { $this->includeStacktraces = $include; if ($this->includeStacktraces) { $this->allowInlineLineBreaks = true; + $this->stacktracesParser = $parser; } + + return $this; } - public function allowInlineLineBreaks($allow = true) + public function allowInlineLineBreaks(bool $allow = true): self { $this->allowInlineLineBreaks = $allow; + + return $this; } - public function ignoreEmptyContextAndExtra($ignore = true) + public function ignoreEmptyContextAndExtra(bool $ignore = true): self { $this->ignoreEmptyContextAndExtra = $ignore; + + return $this; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function format(array $record) + public function format(array $record): string { $vars = parent::format($record); @@ -78,7 +92,6 @@ public function format(array $record) } } - foreach ($vars['context'] as $var => $val) { if (false !== strpos($output, '%context.'.$var.'%')) { $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); @@ -107,12 +120,16 @@ public function format(array $record) // remove leftover %extra.xxx% and %context.xxx% if any if (false !== strpos($output, '%')) { $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + if (null === $output) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } } return $output; } - public function formatBatch(array $records) + public function formatBatch(array $records): string { $message = ''; foreach ($records as $record) { @@ -122,34 +139,37 @@ public function formatBatch(array $records) return $message; } - public function stringify($value) + /** + * @param mixed $value + */ + public function stringify($value): string { return $this->replaceNewlines($this->convertToString($value)); } - protected function normalizeException($e) + protected function normalizeException(\Throwable $e, int $depth = 0): string { - // TODO 2.0 only check for Throwable - if (!$e instanceof \Exception && !$e instanceof \Throwable) { - throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); - } + $str = $this->formatException($e); - $previousText = ''; if ($previous = $e->getPrevious()) { do { - $previousText .= ', '.Utils::getClass($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine(); - } while ($previous = $previous->getPrevious()); - } + $depth++; + if ($depth > $this->maxNormalizeDepth) { + $str .= "\n[previous exception] Over " . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; + break; + } - $str = '[object] ('.Utils::getClass($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')'; - if ($this->includeStacktraces) { - $str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n"; + $str .= "\n[previous exception] " . $this->formatException($previous); + } while ($previous = $previous->getPrevious()); } return $str; } - protected function convertToString($data) + /** + * @param mixed $data + */ + protected function convertToString($data): string { if (null === $data || is_bool($data)) { return var_export($data, true); @@ -159,23 +179,68 @@ protected function convertToString($data) return (string) $data; } - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - return $this->toJson($data, true); - } - - return str_replace('\\/', '/', $this->toJson($data, true)); + return $this->toJson($data, true); } - protected function replaceNewlines($str) + protected function replaceNewlines(string $str): string { if ($this->allowInlineLineBreaks) { if (0 === strpos($str, '{')) { - return str_replace(array('\r', '\n'), array("\r", "\n"), $str); + $str = preg_replace('/(?getCode(); + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $str .= ' faultcode: ' . $e->faultcode; + } + + if (isset($e->faultactor)) { + $str .= ' faultactor: ' . $e->faultactor; + } + + if (isset($e->detail)) { + if (is_string($e->detail)) { + $str .= ' detail: ' . $e->detail; + } elseif (is_object($e->detail) || is_array($e->detail)) { + $str .= ' detail: ' . $this->toJson($e->detail, true); + } + } + } + $str .= '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')'; + + if ($this->includeStacktraces) { + $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)))); } } diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php index 401859bba..29841aa38 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php @@ -1,4 +1,4 @@ -format("Y-m-d\TH:i:s.uO"); - // TODO 2.0 unset the 'datetime' parameter, retained for BC + unset($record["datetime"]); } return parent::format($record); diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php new file mode 100644 index 000000000..b0451aba7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Logmatic. + * + * @author Julien Breux + */ +class LogmaticFormatter extends JsonFormatter +{ + protected const MARKERS = ["sourcecode", "php"]; + + /** + * @var string + */ + protected $hostname = ''; + + /** + * @var string + */ + protected $appname = ''; + + public function setHostname(string $hostname): self + { + $this->hostname = $hostname; + + return $this; + } + + public function setAppname(string $appname): self + { + $this->appname = $appname; + + return $this; + } + + /** + * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic. + * + * @see http://doc.logmatic.io/docs/basics-to-send-data + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record): string + { + if (!empty($this->hostname)) { + $record["hostname"] = $this->hostname; + } + if (!empty($this->appname)) { + $record["appname"] = $this->appname; + } + + $record["@marker"] = static::MARKERS; + + return parent::format($record); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php index 8f83bec04..f8de0d333 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -1,4 +1,4 @@ - */ class LogstashFormatter extends NormalizerFormatter { - const V0 = 0; - const V1 = 1; - /** * @var string the name of the system for the Logstash log message, used to fill the @source field */ @@ -35,108 +32,47 @@ class LogstashFormatter extends NormalizerFormatter protected $applicationName; /** - * @var string a prefix for 'extra' fields from the Monolog record (optional) + * @var string the key for 'extra' fields from the Monolog record */ - protected $extraPrefix; + protected $extraKey; /** - * @var string a prefix for 'context' fields from the Monolog record (optional) + * @var string the key for 'context' fields from the Monolog record */ - protected $contextPrefix; + protected $contextKey; /** - * @var int logstash format version to use + * @param string $applicationName The application that sends the data, used as the "type" field of logstash + * @param string|null $systemName The system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraKey The key for extra keys inside logstash "fields", defaults to extra + * @param string $contextKey The key for context keys inside logstash "fields", defaults to context */ - protected $version; - - /** - * @param string $applicationName the application that sends the data, used as the "type" field of logstash - * @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine - * @param string $extraPrefix prefix for extra keys inside logstash "fields" - * @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_ - * @param int $version the logstash format version to use, defaults to 0 - */ - public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0) + public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context') { // logstash requires a ISO 8601 format date with optional millisecond precision. parent::__construct('Y-m-d\TH:i:s.uP'); - $this->systemName = $systemName ?: gethostname(); + $this->systemName = $systemName === null ? (string) gethostname() : $systemName; $this->applicationName = $applicationName; - $this->extraPrefix = $extraPrefix; - $this->contextPrefix = $contextPrefix; - $this->version = $version; + $this->extraKey = $extraKey; + $this->contextKey = $contextKey; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function format(array $record) + public function format(array $record): string { $record = parent::format($record); - if ($this->version === self::V1) { - $message = $this->formatV1($record); - } else { - $message = $this->formatV0($record); - } - - return $this->toJson($message) . "\n"; - } - - protected function formatV0(array $record) - { if (empty($record['datetime'])) { $record['datetime'] = gmdate('c'); } - $message = array( - '@timestamp' => $record['datetime'], - '@source' => $this->systemName, - '@fields' => array(), - ); - if (isset($record['message'])) { - $message['@message'] = $record['message']; - } - if (isset($record['channel'])) { - $message['@tags'] = array($record['channel']); - $message['@fields']['channel'] = $record['channel']; - } - if (isset($record['level'])) { - $message['@fields']['level'] = $record['level']; - } - if ($this->applicationName) { - $message['@type'] = $this->applicationName; - } - if (isset($record['extra']['server'])) { - $message['@source_host'] = $record['extra']['server']; - } - if (isset($record['extra']['url'])) { - $message['@source_path'] = $record['extra']['url']; - } - if (!empty($record['extra'])) { - foreach ($record['extra'] as $key => $val) { - $message['@fields'][$this->extraPrefix . $key] = $val; - } - } - if (!empty($record['context'])) { - foreach ($record['context'] as $key => $val) { - $message['@fields'][$this->contextPrefix . $key] = $val; - } - } - - return $message; - } - - protected function formatV1(array $record) - { - if (empty($record['datetime'])) { - $record['datetime'] = gmdate('c'); - } - $message = array( + $message = [ '@timestamp' => $record['datetime'], '@version' => 1, 'host' => $this->systemName, - ); + ]; if (isset($record['message'])) { $message['message'] = $record['message']; } @@ -147,20 +83,19 @@ protected function formatV1(array $record) if (isset($record['level_name'])) { $message['level'] = $record['level_name']; } + if (isset($record['level'])) { + $message['monolog_level'] = $record['level']; + } if ($this->applicationName) { $message['type'] = $this->applicationName; } if (!empty($record['extra'])) { - foreach ($record['extra'] as $key => $val) { - $message[$this->extraPrefix . $key] = $val; - } + $message[$this->extraKey] = $record['extra']; } if (!empty($record['context'])) { - foreach ($record['context'] as $key => $val) { - $message[$this->contextPrefix . $key] = $val; - } + $message[$this->contextKey] = $record['context']; } - return $message; + return $this->toJson($message) . "\n"; } } diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php index bd9e4c02b..e66d8fb3c 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php @@ -1,4 +1,4 @@ -maxNestingLevel = max($maxNestingLevel, 0); - $this->exceptionTraceAsString = (bool) $exceptionTraceAsString; + $this->exceptionTraceAsString = $exceptionTraceAsString; } /** * {@inheritDoc} + * + * @return mixed[] */ - public function format(array $record) + public function format(array $record): array { - return $this->formatArray($record); + /** @var mixed[] $res */ + $res = $this->formatArray($record); + + return $res; } /** * {@inheritDoc} + * + * @return array */ - public function formatBatch(array $records) + public function formatBatch(array $records): array { + $formatted = []; foreach ($records as $key => $record) { - $records[$key] = $this->format($record); + $formatted[$key] = $this->format($record); } - return $records; + return $formatted; } - protected function formatArray(array $record, $nestingLevel = 0) + /** + * @param mixed[] $array + * @return mixed[]|string Array except when max nesting level is reached then a string "[...]" + */ + protected function formatArray(array $array, int $nestingLevel = 0) { - if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) { - foreach ($record as $name => $value) { - if ($value instanceof \DateTime) { - $record[$name] = $this->formatDate($value, $nestingLevel + 1); - } elseif ($value instanceof \Exception) { - $record[$name] = $this->formatException($value, $nestingLevel + 1); - } elseif (is_array($value)) { - $record[$name] = $this->formatArray($value, $nestingLevel + 1); - } elseif (is_object($value)) { - $record[$name] = $this->formatObject($value, $nestingLevel + 1); - } + if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) { + return '[...]'; + } + + foreach ($array as $name => $value) { + if ($value instanceof \DateTimeInterface) { + $array[$name] = $this->formatDate($value, $nestingLevel + 1); + } elseif ($value instanceof \Throwable) { + $array[$name] = $this->formatException($value, $nestingLevel + 1); + } elseif (is_array($value)) { + $array[$name] = $this->formatArray($value, $nestingLevel + 1); + } elseif (is_object($value) && !$value instanceof Type) { + $array[$name] = $this->formatObject($value, $nestingLevel + 1); } - } else { - $record = '[...]'; } - return $record; + return $array; } - protected function formatObject($value, $nestingLevel) + /** + * @param mixed $value + * @return mixed[]|string + */ + protected function formatObject($value, int $nestingLevel) { $objectVars = get_object_vars($value); $objectVars['class'] = Utils::getClass($value); @@ -82,14 +102,17 @@ protected function formatObject($value, $nestingLevel) return $this->formatArray($objectVars, $nestingLevel); } - protected function formatException(\Exception $exception, $nestingLevel) + /** + * @return mixed[]|string + */ + protected function formatException(\Throwable $exception, int $nestingLevel) { - $formattedException = array( + $formattedException = [ 'class' => Utils::getClass($exception), 'message' => $exception->getMessage(), 'code' => (int) $exception->getCode(), 'file' => $exception->getFile() . ':' . $exception->getLine(), - ); + ]; if ($this->exceptionTraceAsString === true) { $formattedException['trace'] = $exception->getTraceAsString(); @@ -100,8 +123,8 @@ protected function formatException(\Exception $exception, $nestingLevel) return $this->formatArray($formattedException, $nestingLevel); } - protected function formatDate(\DateTime $value, $nestingLevel) + protected function formatDate(\DateTimeInterface $value, int $nestingLevel): UTCDateTime { - return new \MongoDate($value->getTimestamp()); + return new UTCDateTime((int) floor(((float) $value->format('U.u')) * 1000)); } } diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php index 010466b4a..f926a842f 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -1,4 +1,4 @@ -dateFormat = $dateFormat ?: static::SIMPLE_DATE; - $this->maxDepth = $maxDepth; + $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat; if (!function_exists('json_encode')) { throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); } } /** - * {@inheritdoc} + * {@inheritDoc} + * + * @param mixed[] $record */ public function format(array $record) { @@ -48,7 +56,7 @@ public function format(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ public function formatBatch(array $records) { @@ -59,26 +67,70 @@ public function formatBatch(array $records) return $records; } + public function getDateFormat(): string + { + return $this->dateFormat; + } + + public function setDateFormat(string $dateFormat): self + { + $this->dateFormat = $dateFormat; + + return $this; + } + /** - * @return int + * The maximum number of normalization levels to go through */ - public function getMaxDepth() + public function getMaxNormalizeDepth(): int { - return $this->maxDepth; + return $this->maxNormalizeDepth; + } + + public function setMaxNormalizeDepth(int $maxNormalizeDepth): self + { + $this->maxNormalizeDepth = $maxNormalizeDepth; + + return $this; } /** - * @param int $maxDepth + * The maximum number of items to normalize per level */ - public function setMaxDepth($maxDepth) + public function getMaxNormalizeItemCount(): int { - $this->maxDepth = $maxDepth; + return $this->maxNormalizeItemCount; } - protected function normalize($data, $depth = 0) + public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): self { - if ($depth > $this->maxDepth) { - return 'Over '.$this->maxDepth.' levels deep, aborting normalization'; + $this->maxNormalizeItemCount = $maxNormalizeItemCount; + + return $this; + } + + /** + * Enables `json_encode` pretty print. + */ + public function setJsonPrettyPrint(bool $enable): self + { + if ($enable) { + $this->jsonEncodeOptions |= JSON_PRETTY_PRINT; + } else { + $this->jsonEncodeOptions &= ~JSON_PRETTY_PRINT; + } + + return $this; + } + + /** + * @param mixed $data + * @return null|scalar|array + */ + protected function normalize($data, int $depth = 0) + { + if ($depth > $this->maxNormalizeDepth) { + return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; } if (null === $data || is_scalar($data)) { @@ -95,62 +147,74 @@ protected function normalize($data, $depth = 0) } if (is_array($data)) { - $normalized = array(); + $normalized = []; $count = 1; foreach ($data as $key => $value) { - if ($count++ > 1000) { - $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; + if ($count++ > $this->maxNormalizeItemCount) { + $normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items ('.count($data).' total), aborting normalization'; break; } - $normalized[$key] = $this->normalize($value, $depth+1); + $normalized[$key] = $this->normalize($value, $depth + 1); } return $normalized; } - if ($data instanceof \DateTime) { - return $data->format($this->dateFormat); + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); } if (is_object($data)) { - // TODO 2.0 only check for Throwable - if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) { - return $this->normalizeException($data); + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); } - // non-serializable objects that implement __toString stringified - if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) { + if ($data instanceof \JsonSerializable) { + /** @var null|scalar|array $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(); } else { - // the rest is json-serialized in some way - $value = $this->toJson($data, true); + // the rest is normalized by json encoding and decoding it + /** @var null|scalar|array $value */ + $value = json_decode($this->toJson($data, true), true); } - return sprintf("[object] (%s: %s)", Utils::getClass($data), $value); + return [Utils::getClass($data) => $value]; } if (is_resource($data)) { - return sprintf('[resource] (%s)', get_resource_type($data)); + return sprintf('[resource(%s)]', get_resource_type($data)); } return '[unknown('.gettype($data).')]'; } - protected function normalizeException($e) + /** + * @return mixed[] + */ + protected function normalizeException(Throwable $e, int $depth = 0) { - // TODO 2.0 only check for Throwable - if (!$e instanceof Exception && !$e instanceof \Throwable) { - throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); + if ($depth > $this->maxNormalizeDepth) { + return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization']; } - $data = array( + if ($e instanceof \JsonSerializable) { + return (array) $e->jsonSerialize(); + } + + $data = [ 'class' => Utils::getClass($e), 'message' => $e->getMessage(), 'code' => (int) $e->getCode(), 'file' => $e->getFile().':'.$e->getLine(), - ); + ]; if ($e instanceof \SoapFault) { if (isset($e->faultcode)) { @@ -162,7 +226,7 @@ protected function normalizeException($e) } if (isset($e->detail)) { - if (is_string($e->detail)) { + if (is_string($e->detail)) { $data['detail'] = $e->detail; } elseif (is_object($e->detail) || is_array($e->detail)) { $data['detail'] = $this->toJson($e->detail, true); @@ -178,7 +242,7 @@ protected function normalizeException($e) } if ($previous = $e->getPrevious()) { - $data['previous'] = $this->normalizeException($previous); + $data['previous'] = $this->normalizeException($previous, $depth + 1); } return $data; @@ -188,12 +252,39 @@ protected function normalizeException($e) * Return the JSON representation of a value * * @param mixed $data - * @param bool $ignoreErrors * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string if encoding fails and ignoreErrors is true 'null' is returned + */ + protected function toJson($data, bool $ignoreErrors = false): string + { + return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors); + } + + /** * @return string */ - protected function toJson($data, $ignoreErrors = false) + protected function formatDate(\DateTimeInterface $date) + { + // in case the date format isn't custom then we defer to the custom DateTimeImmutable + // formatting logic, which will pick the right format based on whether useMicroseconds is on + if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof DateTimeImmutable) { + return (string) $date; + } + + return $date->format($this->dateFormat); + } + + public function addJsonEncodeOption(int $option): self { - return Utils::jsonEncode($data, null, $ignoreErrors); + $this->jsonEncodeOptions |= $option; + + return $this; + } + + public function removeJsonEncodeOption(int $option): self + { + $this->jsonEncodeOptions &= ~$option; + + return $this; } -} \ No newline at end of file +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php index 5d345d53c..187bc550d 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php @@ -1,4 +1,4 @@ - $record */ - public function format(array $record) + public function format(array $record): array { + $result = []; foreach ($record as $key => $value) { - $record[$key] = $this->normalizeValue($value); + $result[$key] = $this->normalizeValue($value); } - return $record; + return $result; } /** - * @param mixed $value - * @return mixed + * @param mixed $value + * @return scalar|null */ protected function normalizeValue($value) { $normalized = $this->normalize($value); - if (is_array($normalized) || is_object($normalized)) { + if (is_array($normalized)) { return $this->toJson($normalized, true); } diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php index 65dba99c9..6539b3473 100644 --- a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php +++ b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -1,4 +1,4 @@ - * @author Christophe Coevoet * @author Kirill chEbba Chebunin + * + * @phpstan-import-type Level from \Monolog\Logger */ class WildfireFormatter extends NormalizerFormatter { - const TABLE = 'table'; - /** * Translates Monolog log levels to Wildfire levels. + * + * @var array */ - private $logLevels = array( + private $logLevels = [ Logger::DEBUG => 'LOG', Logger::INFO => 'INFO', Logger::NOTICE => 'INFO', @@ -36,12 +38,25 @@ class WildfireFormatter extends NormalizerFormatter Logger::CRITICAL => 'ERROR', Logger::ALERT => 'ERROR', Logger::EMERGENCY => 'ERROR', - ); + ]; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(?string $dateFormat = null) + { + parent::__construct($dateFormat); + + // http headers do not like non-ISO-8559-1 characters + $this->removeJsonEncodeOption(JSON_UNESCAPED_UNICODE); + } /** - * {@inheritdoc} + * {@inheritDoc} + * + * @return string */ - public function format(array $record) + public function format(array $record): string { // Retrieve the line and file if set and remove them from the formatted extra $file = $line = ''; @@ -54,8 +69,9 @@ public function format(array $record) unset($record['extra']['line']); } + /** @var mixed[] $record */ $record = $this->normalize($record); - $message = array('message' => $record['message']); + $message = ['message' => $record['message']]; $handleError = false; if ($record['context']) { $message['context'] = $record['context']; @@ -69,42 +85,52 @@ public function format(array $record) $message = reset($message); } - if (isset($record['context'][self::TABLE])) { + if (isset($record['context']['table'])) { $type = 'TABLE'; $label = $record['channel'] .': '. $record['message']; - $message = $record['context'][self::TABLE]; + $message = $record['context']['table']; } else { $type = $this->logLevels[$record['level']]; $label = $record['channel']; } // Create JSON object describing the appearance of the message in the console - $json = $this->toJson(array( - array( + $json = $this->toJson([ + [ 'Type' => $type, 'File' => $file, 'Line' => $line, 'Label' => $label, - ), + ], $message, - ), $handleError); + ], $handleError); // The message itself is a serialization of the above JSON object + it's length return sprintf( - '%s|%s|', + '%d|%s|', strlen($json), $json ); } + /** + * {@inheritDoc} + * + * @phpstan-return never + */ public function formatBatch(array $records) { throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); } - protected function normalize($data, $depth = 0) + /** + * {@inheritDoc} + * + * @return null|scalar|array|object + */ + protected function normalize($data, int $depth = 0) { - if (is_object($data) && !$data instanceof \DateTime) { + if (is_object($data) && !$data instanceof \DateTimeInterface) { return $data; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php index cdd9f7d40..a5cdaa71f 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -abstract class AbstractHandler implements HandlerInterface, ResettableInterface +abstract class AbstractHandler extends Handler implements ResettableInterface { - protected $level = Logger::DEBUG; - protected $bubble = true; - /** - * @var FormatterInterface + * @var int + * @phpstan-var Level */ - protected $formatter; - protected $processors = array(); + protected $level = Logger::DEBUG; + /** @var bool */ + protected $bubble = true; /** * @param int|string $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 + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ - public function __construct($level = Logger::DEBUG, $bubble = true) + public function __construct($level = Logger::DEBUG, bool $bubble = true) { $this->setLevel($level); $this->bubble = $bubble; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return $record['level'] >= $this->level; } - /** - * {@inheritdoc} - */ - public function handleBatch(array $records) - { - foreach ($records as $record) { - $this->handle($record); - } - } - - /** - * Closes the handler. - * - * This will be called automatically when the object is destroyed - */ - public function close() - { - } - - /** - * {@inheritdoc} - */ - public function pushProcessor($callback) - { - if (!is_callable($callback)) { - throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); - } - array_unshift($this->processors, $callback); - - return $this; - } - - /** - * {@inheritdoc} - */ - public function popProcessor() - { - if (!$this->processors) { - throw new \LogicException('You tried to pop from an empty processor stack.'); - } - - return array_shift($this->processors); - } - - /** - * {@inheritdoc} - */ - public function setFormatter(FormatterInterface $formatter) - { - $this->formatter = $formatter; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getFormatter() - { - if (!$this->formatter) { - $this->formatter = $this->getDefaultFormatter(); - } - - return $this->formatter; - } - /** * Sets minimum logging level at which this handler will be triggered. * - * @param int|string $level Level or level name + * @param Level|LevelName|LogLevel::* $level Level or level name * @return self */ - public function setLevel($level) + public function setLevel($level): self { $this->level = Logger::toMonologLevel($level); @@ -133,8 +70,10 @@ public function setLevel($level) * Gets minimum logging level at which this handler will be triggered. * * @return int + * + * @phpstan-return Level */ - public function getLevel() + public function getLevel(): int { return $this->level; } @@ -146,7 +85,7 @@ public function getLevel() * false means that bubbling is not permitted. * @return self */ - public function setBubble($bubble) + public function setBubble(bool $bubble): self { $this->bubble = $bubble; @@ -159,38 +98,15 @@ public function setBubble($bubble) * @return bool true means that this handler allows bubbling. * false means that bubbling is not permitted. */ - public function getBubble() + public function getBubble(): bool { return $this->bubble; } - public function __destruct() - { - try { - $this->close(); - } catch (\Exception $e) { - // do nothing - } catch (\Throwable $e) { - // do nothing - } - } - - public function reset() - { - foreach ($this->processors as $processor) { - if ($processor instanceof ResettableInterface) { - $processor->reset(); - } - } - } - /** - * Gets the default formatter. - * - * @return FormatterInterface + * {@inheritDoc} */ - protected function getDefaultFormatter() + public function reset() { - return new LineFormatter(); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php index e1e89530a..77e533fca 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php @@ -1,4 +1,4 @@ - * @author Christophe Coevoet + * + * @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 +abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + use FormattableHandlerTrait; + /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; } - $record = $this->processRecord($record); + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } $record['formatted'] = $this->getFormatter()->format($record); @@ -44,25 +53,17 @@ public function handle(array $record) /** * Writes the record down to the log of the implementing handler * - * @param array $record - * @return void + * @phpstan-param FormattedRecord $record */ - abstract protected function write(array $record); + abstract protected function write(array $record): void; /** - * Processes a record. - * - * @param array $record - * @return array + * @return void */ - protected function processRecord(array $record) + public function reset() { - if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } - } + parent::reset(); - return $record; + $this->resetProcessors(); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php index 8c76aca0b..5e5ad1c1f 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php @@ -1,4 +1,4 @@ - */ - protected $logLevels = array( + protected $logLevels = [ Logger::DEBUG => LOG_DEBUG, Logger::INFO => LOG_INFO, Logger::NOTICE => LOG_NOTICE, @@ -33,12 +39,13 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler Logger::CRITICAL => LOG_CRIT, Logger::ALERT => LOG_ALERT, Logger::EMERGENCY => LOG_EMERG, - ); + ]; /** * List of valid log facility names. + * @var array */ - protected $facilities = array( + protected $facilities = [ 'auth' => LOG_AUTH, 'authpriv' => LOG_AUTHPRIV, 'cron' => LOG_CRON, @@ -50,14 +57,12 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler 'syslog' => LOG_SYSLOG, 'user' => LOG_USER, 'uucp' => LOG_UUCP, - ); + ]; /** - * @param mixed $facility - * @param 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|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant */ - public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true) + public function __construct($facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true) { parent::__construct($level, $bubble); @@ -82,7 +87,7 @@ public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubbl } // convert textual description of facility to syslog constant - if (array_key_exists(strtolower($facility), $this->facilities)) { + if (is_string($facility) && array_key_exists(strtolower($facility), $this->facilities)) { $facility = $this->facilities[strtolower($facility)]; } elseif (!in_array($facility, array_values($this->facilities), true)) { throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); @@ -92,9 +97,9 @@ public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubbl } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php index e5a46bc0d..994872ce8 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -1,4 +1,4 @@ - */ + private $extraAttributes = []; + + /** + * @return array + */ + public function getExtraAttributes(): array + { + return $this->extraAttributes; + } + + /** + * Configure extra attributes to pass to the AMQPExchange (if you are using the amqp extension) + * + * @param array $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 @@ -31,18 +60,16 @@ class AmqpHandler extends AbstractProcessingHandler /** * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use - * @param string $exchangeName - * @param int $level - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string|null $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only */ - public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true) + public function __construct($exchange, ?string $exchangeName = null, $level = Logger::DEBUG, bool $bubble = true) { - if ($exchange instanceof AMQPExchange) { - $exchange->setName($exchangeName); - } elseif ($exchange instanceof AMQPChannel) { - $this->exchangeName = $exchangeName; - } else { + if ($exchange instanceof AMQPChannel) { + $this->exchangeName = (string) $exchangeName; + } elseif (!$exchange instanceof AMQPExchange) { throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required'); + } elseif ($exchangeName) { + @trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED); } $this->exchange = $exchange; @@ -52,20 +79,24 @@ public function __construct($exchange, $exchangeName = 'log', $level = Logger::D /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $data = $record["formatted"]; $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, - array( - 'delivery_mode' => 2, - 'content_type' => 'application/json', - ) + $attributes ); } else { $this->exchange->basic_publish( @@ -79,7 +110,7 @@ protected function write(array $record) /** * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { if ($this->exchange instanceof AMQPExchange) { parent::handleBatch($records); @@ -92,6 +123,7 @@ public function handleBatch(array $records) continue; } + /** @var Record $record */ $record = $this->processRecord($record); $data = $this->getFormatter()->format($record); @@ -108,40 +140,31 @@ public function handleBatch(array $records) /** * Gets the routing key for the AMQP exchange * - * @param array $record - * @return string + * @phpstan-param Record $record */ - protected function getRoutingKey(array $record) + protected function getRoutingKey(array $record): string { - $routingKey = sprintf( - '%s.%s', - // TODO 2.0 remove substr call - substr($record['level_name'], 0, 4), - $record['channel'] - ); + $routingKey = sprintf('%s.%s', $record['level_name'], $record['channel']); return strtolower($routingKey); } - /** - * @param string $data - * @return AMQPMessage - */ - private function createAmqpMessage($data) + private function createAmqpMessage(string $data): AMQPMessage { - return new AMQPMessage( - (string) $data, - array( - '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); } /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php index 68feb4808..95bbfed42 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class BrowserConsoleHandler extends AbstractProcessingHandler { + /** @var bool */ protected static $initialized = false; - protected static $records = array(); + /** @var FormattedRecord[] */ + protected static $records = []; + + protected const FORMAT_HTML = 'html'; + protected const FORMAT_JS = 'js'; + protected const FORMAT_UNKNOWN = 'unknown'; /** * {@inheritDoc} @@ -32,7 +50,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler * * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); } @@ -40,7 +58,7 @@ protected function getDefaultFormatter() /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { // Accumulate records static::$records[] = $record; @@ -56,57 +74,57 @@ protected function write(array $record) * Convert records to javascript console commands and send it to the browser. * This method is automatically called on PHP shutdown if output is HTML or Javascript. */ - public static function send() + public static function send(): void { $format = static::getResponseFormat(); - if ($format === 'unknown') { + if ($format === self::FORMAT_UNKNOWN) { return; } if (count(static::$records)) { - if ($format === 'html') { + if ($format === self::FORMAT_HTML) { static::writeOutput(''); - } elseif ($format === 'js') { + } elseif ($format === self::FORMAT_JS) { static::writeOutput(static::generateScript()); } static::resetStatic(); } } - public function close() + public function close(): void { self::resetStatic(); } public function reset() { + parent::reset(); + self::resetStatic(); } /** * Forget all logged records */ - public static function resetStatic() + public static function resetStatic(): void { - static::$records = array(); + static::$records = []; } /** * Wrapper for register_shutdown_function to allow overriding */ - protected function registerShutdownFunction() + protected function registerShutdownFunction(): void { if (PHP_SAPI !== 'cli') { - register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']); } } /** * Wrapper for echo to allow overriding - * - * @param string $str */ - protected static function writeOutput($str) + protected static function writeOutput(string $str): void { echo $str; } @@ -119,42 +137,55 @@ protected static function writeOutput($str) * If Content-Type is anything else -> unknown * * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* */ - protected static function getResponseFormat() + protected static function getResponseFormat(): string { // Check content type foreach (headers_list() as $header) { if (stripos($header, 'content-type:') === 0) { - // This handler only works with HTML and javascript outputs - // text/javascript is obsolete in favour of application/javascript, but still used - if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { - return 'js'; - } - if (stripos($header, 'text/html') === false) { - return 'unknown'; - } - break; + return static::getResponseFormatFromContentType($header); } } - return 'html'; + return self::FORMAT_HTML; } - private static function generateScript() + /** + * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* + */ + protected static function getResponseFormatFromContentType(string $contentType): string { - $script = array(); + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) { + return self::FORMAT_JS; + } + + if (stripos($contentType, 'text/html') !== false) { + return self::FORMAT_HTML; + } + + return self::FORMAT_UNKNOWN; + } + + private static function generateScript(): string + { + $script = []; foreach (static::$records as $record) { $context = static::dump('Context', $record['context']); $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, - array(static::call_array('groupCollapsed', static::handleStyles($record['formatted']))), + $script = array_merge( + $script, + [static::call_array('groupCollapsed', static::handleStyles($record['formatted']))], $context, $extra, - array(static::call('groupEnd')) + [static::call('groupEnd')] ); } } @@ -162,9 +193,26 @@ private static function generateScript() return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; } - private static function handleStyles($formatted) + 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[] + */ + private static function handleStyles(string $formatted): array { - $args = array(); + $args = []; $format = '%c' . $formatted; preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); @@ -173,7 +221,7 @@ private static function handleStyles($formatted) $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0])); $pos = $match[0][1]; - $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0])); + $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + strlen($match[0][0])); } $args[] = static::quote('font-weight: normal'); @@ -182,12 +230,12 @@ private static function handleStyles($formatted) return array_reverse($args); } - private static function handleCustomStyles($style, $string) + private static function handleCustomStyles(string $style, string $string): string { - static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); - static $labels = array(); + static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; + static $labels = []; - return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($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])) { @@ -200,11 +248,22 @@ private static function handleCustomStyles($style, $string) return $m[1]; }, $style); + + if (null === $style) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $style; } - private static function dump($title, array $dict) + /** + * @param mixed[] $dict + * @return mixed[] + */ + private static function dump(string $title, array $dict): array { - $script = array(); + $script = []; $dict = array_filter($dict); if (empty($dict)) { return $script; @@ -215,26 +274,34 @@ private static function dump($title, array $dict) if (empty($value)) { $value = static::quote(''); } - $script[] = static::call('log', static::quote('%s: %o'), static::quote($key), $value); + $script[] = static::call('log', static::quote('%s: %o'), static::quote((string) $key), $value); } return $script; } - private static function quote($arg) + private static function quote(string $arg): string { return '"' . addcslashes($arg, "\"\n\\") . '"'; } - private static function call() + /** + * @param mixed $args + */ + private static function call(...$args): string { - $args = func_get_args(); $method = array_shift($args); + if (!is_string($method)) { + throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true)); + } return static::call_array($method, $args); } - private static function call_array($method, array $args) + /** + * @param mixed[] $args + */ + private static function call_array(string $method, array $args): string { return 'c.' . $method . '(' . implode(', ', $args) . ');'; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php index 0957e5580..fcce5d630 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ -class BufferHandler extends AbstractHandler +class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + + /** @var HandlerInterface */ protected $handler; + /** @var int */ protected $bufferSize = 0; + /** @var int */ protected $bufferLimit; + /** @var bool */ protected $flushOnOverflow; - protected $buffer = array(); + /** @var Record[] */ + protected $buffer = []; + /** @var bool */ protected $initialized = false; /** * @param HandlerInterface $handler Handler. * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. - * @param 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 $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded */ - public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) + public function __construct(HandlerInterface $handler, int $bufferLimit = 0, $level = Logger::DEBUG, bool $bubble = true, bool $flushOnOverflow = false) { parent::__construct($level, $bubble); $this->handler = $handler; - $this->bufferLimit = (int) $bufferLimit; + $this->bufferLimit = $bufferLimit; $this->flushOnOverflow = $flushOnOverflow; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($record['level'] < $this->level) { return false; @@ -58,7 +66,7 @@ public function handle(array $record) if (!$this->initialized) { // __destructor() doesn't get called on Fatal errors - register_shutdown_function(array($this, 'close')); + register_shutdown_function([$this, 'close']); $this->initialized = true; } @@ -72,9 +80,8 @@ public function handle(array $record) } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } $this->buffer[] = $record; @@ -83,7 +90,7 @@ public function handle(array $record) return false === $this->bubble; } - public function flush() + public function flush(): void { if ($this->bufferSize === 0) { return; @@ -101,20 +108,22 @@ public function __destruct() } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { $this->flush(); + + $this->handler->close(); } /** * Clears the buffer without flushing any messages down to the wrapped handler. */ - public function clear() + public function clear(): void { $this->bufferSize = 0; - $this->buffer = array(); + $this->buffer = []; } public function reset() @@ -123,26 +132,36 @@ public function reset() parent::reset(); + $this->resetProcessors(); + if ($this->handler instanceof ResettableInterface) { $this->handler->reset(); } } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->handler->setFormatter($formatter); + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->handler->getFormatter(); + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php index 47120e545..234ecf614 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ class ChromePHPHandler extends AbstractProcessingHandler { + use WebRequestRecognizerTrait; + /** * Version of the extension */ - const VERSION = '4.0'; + protected const VERSION = '4.0'; /** * Header name */ - const HEADER_NAME = 'X-ChromeLogger-Data'; + protected const HEADER_NAME = 'X-ChromeLogger-Data'; /** * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) */ - const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + protected const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + /** @var bool */ protected static $initialized = false; /** @@ -50,19 +56,17 @@ class ChromePHPHandler extends AbstractProcessingHandler */ protected static $overflowed = false; - protected static $json = array( + /** @var mixed[] */ + protected static $json = [ 'version' => self::VERSION, - 'columns' => array('label', 'log', 'backtrace', 'type'), - 'rows' => array(), - ); + 'columns' => ['label', 'log', 'backtrace', 'type'], + 'rows' => [], + ]; + /** @var bool */ protected static $sendHeaders = true; - /** - * @param 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 - */ - public function __construct($level = Logger::DEBUG, $bubble = true) + public function __construct($level = Logger::DEBUG, bool $bubble = true) { parent::__construct($level, $bubble); if (!function_exists('json_encode')) { @@ -71,17 +75,23 @@ public function __construct($level = Logger::DEBUG, $bubble = true) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { - $messages = array(); + if (!$this->isWebRequest()) { + return; + } + + $messages = []; foreach ($records as $record) { if ($record['level'] < $this->level) { continue; } - $messages[] = $this->processRecord($record); + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; } if (!empty($messages)) { @@ -94,7 +104,7 @@ public function handleBatch(array $records) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new ChromePHPFormatter(); } @@ -104,10 +114,13 @@ protected function getDefaultFormatter() * * @see sendHeader() * @see send() - * @param array $record */ - protected function write(array $record) + protected function write(array $record): void { + if (!$this->isWebRequest()) { + return; + } + self::$json['rows'][] = $record['formatted']; $this->send(); @@ -118,7 +131,7 @@ protected function write(array $record) * * @see sendHeader() */ - protected function send() + protected function send(): void { if (self::$overflowed || !self::$sendHeaders) { return; @@ -132,40 +145,37 @@ protected function send() return; } - self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; } - $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 (strlen($data) > 3 * 1024) { self::$overflowed = true; - $record = array( + $record = [ 'message' => 'Incomplete logs, chrome header size limit reached', - 'context' => array(), + 'context' => [], 'level' => Logger::WARNING, 'level_name' => Logger::getLevelName(Logger::WARNING), 'channel' => 'monolog', - 'datetime' => new \DateTime(), - 'extra' => array(), - ); + 'datetime' => new \DateTimeImmutable(), + '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) !== '') { - $this->sendHeader(self::HEADER_NAME, $data); + $this->sendHeader(static::HEADER_NAME, $data); } } /** * Send header string to the client - * - * @param string $header - * @param string $content */ - protected function sendHeader($header, $content) + protected function sendHeader(string $header, string $content): void { if (!headers_sent() && self::$sendHeaders) { header(sprintf('%s: %s', $header, $content)); @@ -174,39 +184,13 @@ protected function sendHeader($header, $content) /** * Verifies if the headers are accepted by the current user agent - * - * @return bool */ - protected function headersAccepted() + protected function headersAccepted(): bool { if (empty($_SERVER['HTTP_USER_AGENT'])) { return false; } - return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']); - } - - /** - * BC getter for the sendHeaders property that has been made static - */ - public function __get($property) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - return static::$sendHeaders; - } - - /** - * BC setter for the sendHeaders property that has been made static - */ - public function __set($property, $value) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - static::$sendHeaders = $value; + return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php index cc9869719..526576132 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -1,4 +1,4 @@ -options = array_merge(array( + $this->options = array_merge([ 'host' => 'localhost', 'port' => 5984, 'dbname' => 'logger', 'username' => null, 'password' => null, - ), $options); + ], $options); parent::__construct($level, $bubble); } @@ -39,7 +44,7 @@ public function __construct(array $options = array(), $level = Logger::DEBUG, $b /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $basicAuth = null; if ($this->options['username']) { @@ -47,17 +52,17 @@ protected function write(array $record) } $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; - $context = stream_context_create(array( - 'http' => array( + $context = stream_context_create([ + 'http' => [ 'method' => 'POST', 'content' => $record['formatted'], 'ignore_errors' => true, 'max_redirects' => 0, 'header' => 'Content-type: application/json', - ), - )); + ], + ]); - if (false === @file_get_contents($url, null, $context)) { + if (false === @file_get_contents($url, false, $context)) { throw new \RuntimeException(sprintf('Could not connect to %s', $url)); } } @@ -65,7 +70,7 @@ protected function write(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php index 44928efb2..3535a4fcd 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -1,4 +1,4 @@ - + * @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 { - private $udpConnection; - private $httpConnection; + /** @var resource|\Socket|null */ + private $udpConnection = null; + /** @var resource|\CurlHandle|null */ + private $httpConnection = null; + /** @var string */ private $scheme; + /** @var string */ private $host; + /** @var int */ private $port; - private $acceptedSchemes = array('http', 'udp'); + /** @var string[] */ + private $acceptedSchemes = ['http', 'udp']; /** * Create a Cube handler @@ -36,23 +43,24 @@ class CubeHandler extends AbstractProcessingHandler * A valid url must consist of three parts : protocol://host:port * Only valid protocols used by Cube are http and udp */ - public function __construct($url, $level = Logger::DEBUG, $bubble = true) + public function __construct(string $url, $level = Logger::DEBUG, bool $bubble = true) { $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'); } if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) { throw new \UnexpectedValueException( 'Invalid protocol (' . $urlInfo['scheme'] . ').' - . ' Valid options are ' . implode(', ', $this->acceptedSchemes)); + . ' Valid options are ' . implode(', ', $this->acceptedSchemes) + ); } $this->scheme = $urlInfo['scheme']; $this->host = $urlInfo['host']; - $this->port = $urlInfo['port']; + $this->port = (int) $urlInfo['port']; parent::__construct($level, $bubble); } @@ -63,50 +71,53 @@ public function __construct($url, $level = Logger::DEBUG, $bubble = true) * @throws \LogicException when unable to connect to the socket * @throws MissingExtensionException when there is no socket extension */ - protected function connectUdp() + protected function connectUdp(): void { if (!extension_loaded('sockets')) { 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); } } /** - * Establish a connection to a http server - * @throws \LogicException when no curl extension + * Establish a connection to an http server + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when no curl extension */ - protected function connectHttp() + protected function connectHttp(): void { if (!extension_loaded('curl')) { - throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); + 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); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $date = $record['datetime']; - $data = array('time' => $date->format('Y-m-d\TH:i:s.uO')); + $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')]; unset($record['datetime']); if (isset($record['context']['type'])) { @@ -126,7 +137,7 @@ protected function write(array $record) } } - private function writeUdp($data) + private function writeUdp(string $data): void { if (!$this->udpConnection) { $this->connectUdp(); @@ -135,17 +146,21 @@ private function writeUdp($data) socket_send($this->udpConnection, $data, strlen($data), 0); } - private function writeHttp($data) + private function writeHttp(string $data): void { if (!$this->httpConnection) { $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, array( + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Content-Length: ' . strlen('['.$data.']'), - )); + ]); Curl\Util::execute($this->httpConnection, 5, false); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php index 48d30b358..58f63389b 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -1,4 +1,4 @@ - */ + private static $retriableErrorCodes = [ CURLE_COULDNT_RESOLVE_HOST, CURLE_COULDNT_CONNECT, CURLE_HTTP_NOT_FOUND, @@ -21,37 +29,43 @@ class Util CURLE_OPERATION_TIMEOUTED, CURLE_HTTP_POST_ERROR, CURLE_SSL_CONNECT_ERROR, - ); + ]; /** * Executes a CURL request with optional retries and exception on failure * - * @param resource $ch curl handler - * @throws \RuntimeException + * @param resource|CurlHandle $ch curl handler + * @param int $retries + * @param bool $closeAfterDone + * @return bool|string @see curl_exec */ - public static function execute($ch, $retries = 5, $closeAfterDone = true) + public static function execute($ch, int $retries = 5, bool $closeAfterDone = true) { while ($retries--) { - if (curl_exec($ch) === false) { + $curlResponse = curl_exec($ch); + if ($curlResponse === false) { $curlErrno = curl_errno($ch); if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) { $curlError = curl_error($ch); - if ($closeAfterDone) { + if (\PHP_VERSION_ID < 80000 && $closeAfterDone) { curl_close($ch); } - throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError)); + throw new \RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError)); } continue; } - if ($closeAfterDone) { + if (\PHP_VERSION_ID < 80000 && $closeAfterDone) { curl_close($ch); } - break; + + return $curlResponse; } + + return false; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php index 35b55cb4f..9b85ae7ed 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -1,4 +1,4 @@ - + * + * @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 { @@ -41,7 +46,7 @@ class DeduplicationHandler extends BufferHandler protected $deduplicationStore; /** - * @var int + * @var Level */ protected $deduplicationLevel; @@ -58,11 +63,13 @@ class DeduplicationHandler extends BufferHandler /** * @param HandlerInterface $handler Handler. * @param string $deduplicationStore The file/path where the deduplication log should be kept - * @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @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, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true) + public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, int $time = 60, bool $bubble = true) { parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); @@ -71,7 +78,7 @@ public function __construct(HandlerInterface $handler, $deduplicationStore = nul $this->time = $time; } - public function flush() + public function flush(): void { if ($this->bufferSize === 0) { return; @@ -81,7 +88,6 @@ public function flush() foreach ($this->buffer as $record) { if ($record['level'] >= $this->deduplicationLevel) { - $passthru = $passthru || !$this->isDuplicate($record); if ($passthru) { $this->appendRecord($record); @@ -101,7 +107,10 @@ public function flush() } } - private function isDuplicate(array $record) + /** + * @phpstan-param Record $record + */ + private function isDuplicate(array $record): bool { if (!file_exists($this->deduplicationStore)) { return false; @@ -131,21 +140,26 @@ private function isDuplicate(array $record) return false; } - private function collectLogs() + private function collectLogs(): void { if (!file_exists($this->deduplicationStore)) { - return false; + return; } $handle = fopen($this->deduplicationStore, 'rw+'); + + if (!$handle) { + throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore); + } + flock($handle, LOCK_EX); - $validLogs = array(); + $validLogs = []; $timestampValidity = time() - $this->time; while (!feof($handle)) { $log = fgets($handle); - if (substr($log, 0, 10) >= $timestampValidity) { + if ($log && substr($log, 0, 10) >= $timestampValidity) { $validLogs[] = $log; } } @@ -162,7 +176,10 @@ private function collectLogs() $this->gc = false; } - private function appendRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function appendRecord(array $record): void { file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php index b91ffec90..ebd52c3a0 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -1,4 +1,4 @@ -client = $client; parent::__construct($level, $bubble); @@ -33,12 +35,12 @@ public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubb /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->client->postDocument($record['formatted']); } - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php index 8846e0a08..21840bf60 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -1,4 +1,4 @@ -=')) { $this->version = 3; $this->marshaler = new Marshaler; @@ -69,9 +65,9 @@ public function __construct(DynamoDbClient $client, $table, $level = Logger::DEB } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $filtered = $this->filterEmptyFields($record['formatted']); if ($this->version === 3) { @@ -81,17 +77,17 @@ protected function write(array $record) $formatted = $this->client->formatAttributes($filtered); } - $this->client->putItem(array( + $this->client->putItem([ 'TableName' => $this->table, 'Item' => $formatted, - )); + ]); } /** - * @param array $record - * @return array + * @param mixed[] $record + * @return mixed[] */ - protected function filterEmptyFields(array $record) + protected function filterEmptyFields(array $record): array { return array_filter($record, function ($value) { return !empty($value) || false === $value || 0 === $value; @@ -99,9 +95,9 @@ protected function filterEmptyFields(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new ScalarFormatter(self::DATE_FORMAT); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php index bb0f83ebc..264b380d7 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php @@ -1,4 +1,4 @@ -setHosts($hosts) + * ->build(); * - * $client = new \Elastica\Client(); * $options = array( * 'index' => 'elastic_index_name', - * 'type' => 'elastic_doc_type', + * 'type' => 'elastic_doc_type', * ); - * $handler = new ElasticSearchHandler($client, $options); + * $handler = new ElasticsearchHandler($client, $options); * $log = new Logger('application'); * $log->pushHandler($handler); * - * @author Jelle Vink + * @author Avtandil Kikabidze */ -class ElasticSearchHandler extends AbstractProcessingHandler +class ElasticsearchHandler extends AbstractProcessingHandler { /** - * @var Client + * @var Client|Client8 */ protected $client; /** - * @var array Handler config options + * @var mixed[] Handler config options + */ + protected $options = []; + + /** + * @var bool */ - protected $options = array(); + private $needsType; /** - * @param Client $client Elastica Client object - * @param array $options Handler configuration - * @param 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 Client|Client8 $client Elasticsearch Client object + * @param mixed[] $options Handler configuration */ - public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true) + 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( - array( - 'index' => 'monolog', // Elastic index name - 'type' => 'record', // Elastic document type - 'ignore_error' => false, // Suppress Elastica exceptions - ), + [ + 'index' => 'monolog', // Elastic index name + 'type' => '_doc', // Elastic document type + 'ignore_error' => false, // Suppress Elasticsearch exceptions + ], $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; + } } /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { - $this->bulkSend(array($record['formatted'])); + $this->bulkSend([$record['formatted']]); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - if ($formatter instanceof ElasticaFormatter) { + if ($formatter instanceof ElasticsearchFormatter) { return parent::setFormatter($formatter); } - throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter'); + + throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter'); } /** * Getter options - * @return array + * + * @return mixed[] */ - public function getOptions() + public function getOptions(): array { return $this->options; } @@ -96,15 +124,15 @@ public function getOptions() /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { - return new ElasticaFormatter($this->options['index'], $this->options['type']); + return new ElasticsearchFormatter($this->options['index'], $this->options['type']); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { $documents = $this->getFormatter()->formatBatch($records); $this->bulkSend($documents); @@ -112,17 +140,80 @@ public function handleBatch(array $records) /** * Use Elasticsearch bulk API to send list of documents - * @param array $documents + * + * @param array[] $records Records + _index/_type keys * @throws \RuntimeException */ - protected function bulkSend(array $documents) + protected function bulkSend(array $records): void { try { - $this->client->addDocuments($documents); - } catch (ExceptionInterface $e) { - if (!$this->options['ignore_error']) { - throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + $params = [ + 'body' => [], + ]; + + foreach ($records as $record) { + $params['body'][] = [ + 'index' => $this->needsType ? [ + '_index' => $record['_index'], + '_type' => $record['_type'], + ] : [ + '_index' => $record['_index'], + ], + ]; + unset($record['_index'], $record['_type']); + + $params['body'][] = $record; + } + + /** @var Elasticsearch */ + $responses = $this->client->bulk($params); + + if ($responses['errors'] === true) { + throw $this->createExceptionFromResponses($responses); } + } catch (Throwable $e) { + if (! $this->options['ignore_error']) { + throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e); + } + } + } + + /** + * Creates elasticsearch exception from responses array + * + * Only the first error is converted into an exception. + * + * @param mixed[]|Elasticsearch $responses returned by $this->client->bulk() + */ + protected function createExceptionFromResponses($responses): Throwable + { + // @phpstan-ignore offsetAccess.nonOffsetAccessible + foreach ($responses['items'] ?? [] as $item) { + if (isset($item['index']['error'])) { + return $this->createExceptionFromError($item['index']['error']); + } + } + + 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.'); + } + + /** + * Creates elasticsearch exception from error array + * + * @param mixed[] $error + */ + 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); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php new file mode 100644 index 000000000..fc92ca42d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Elastica\Document; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', Types have been removed in Elastica 7 + * ); + * $handler = new ElasticaHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + */ +class ElasticaHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var mixed[] Handler config options + */ + protected $options = []; + + /** + * @param Client $client Elastica Client object + * @param mixed[] $options Handler configuration + */ + public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ], + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->bulkSend([$record['formatted']]); + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + + throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter'); + } + + /** + * @return mixed[] + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param Document[] $documents + * + * @throws \RuntimeException + */ + protected function bulkSend(array $documents): void + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php index b2986b0fe..f2e22036b 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -1,4 +1,4 @@ -expandNewlines) { - $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); - foreach ($lines as $line) { - error_log($line, $this->messageType); - } - } else { + if (!$this->expandNewlines) { error_log((string) $record['formatted'], $this->messageType); + + return; + } + + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + if ($lines === false) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. Utils::pcreLastErrorMessage($pcreErrorCode)); + } + foreach ($lines as $line) { + error_log($line, $this->messageType); } } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php new file mode 100644 index 000000000..d4e234ce0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +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 +{ + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + break; + } catch (Throwable $e) { + // What throwable? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->processors) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + /** @var Record[] $records */ + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + break; + } catch (Throwable $e) { + // What throwable? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php index 949f22718..5e43e1dc2 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class FilterHandler extends AbstractHandler +class FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + /** * Handler or factory callable($record, $this) * - * @var callable|\Monolog\Handler\HandlerInterface + * @var callable|HandlerInterface + * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface */ protected $handler; @@ -35,6 +44,7 @@ class FilterHandler extends AbstractHandler * Minimum level for logs that are passed to handler * * @var int[] + * @phpstan-var array */ protected $acceptedLevels; @@ -46,12 +56,17 @@ class FilterHandler extends AbstractHandler protected $bubble; /** + * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler + * * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $filterHandler). * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided - * @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param int|string $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel */ - public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true) + public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, bool $bubble = true) { $this->handler = $handler; $this->bubble = $bubble; @@ -63,9 +78,9 @@ public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel } /** - * @return array + * @phpstan-return array */ - public function getAcceptedLevels() + public function getAcceptedLevels(): array { return array_flip($this->acceptedLevels); } @@ -73,8 +88,11 @@ public function getAcceptedLevels() /** * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + * + * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel */ - public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY) + public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY): self { if (is_array($minLevelOrList)) { $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList); @@ -86,29 +104,30 @@ public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = L })); } $this->acceptedLevels = array_flip($acceptedLevels); + + return $this; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return isset($this->acceptedLevels[$record['level']]); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } $this->getHandler($record)->handle($record); @@ -117,11 +136,11 @@ public function handle(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { - $filtered = array(); + $filtered = []; foreach ($records as $record) { if ($this->isHandling($record)) { $filtered[] = $record; @@ -139,11 +158,13 @@ public function handleBatch(array $records) * If the handler was provided as a factory callable, this will trigger the handler's instantiation. * * @return HandlerInterface + * + * @phpstan-param Record $record */ - public function getHandler(array $record = null) + public function getHandler(?array $record = null) { if (!$this->handler instanceof HandlerInterface) { - $this->handler = call_user_func($this->handler, $record, $this); + $this->handler = ($this->handler)($record, $this); if (!$this->handler instanceof HandlerInterface) { throw new \RuntimeException("The factory callable should return a HandlerInterface"); } @@ -153,20 +174,39 @@ public function getHandler(array $record = null) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->getHandler()->setFormatter($formatter); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->getHandler()->getFormatter(); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + public function reset() + { + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php index aaca12ccd..0aa5607b1 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ interface ActivationStrategyInterface { /** * Returns whether the given record activates the handler. * - * @param array $record - * @return bool + * @phpstan-param Record $record */ - public function isHandlerActivated(array $record); + public function isHandlerActivated(array $record): bool; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php index 2a2a64d94..7b9abb582 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -1,4 +1,4 @@ - * * @author Mike Meessen + * + * @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 Level + */ private $defaultActionLevel; + + /** + * @var array + */ private $channelToActionLevel; /** - * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any - * @param array $channelToActionLevel An array that maps channel names to action levels. + * @param int|string $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + * + * @phpstan-param array $channelToActionLevel + * @phpstan-param Level|LevelName|LogLevel::* $defaultActionLevel */ - public function __construct($defaultActionLevel, $channelToActionLevel = array()) + public function __construct($defaultActionLevel, array $channelToActionLevel = []) { $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); } - public function isHandlerActivated(array $record) + /** + * @phpstan-param Record $record + */ + public function isHandlerActivated(array $record): bool { if (isset($this->channelToActionLevel[$record['channel']])) { return $record['level'] >= $this->channelToActionLevel[$record['channel']]; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php index 6e630852f..5ec88eab6 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class ErrorLevelActivationStrategy implements ActivationStrategyInterface { + /** + * @var Level + */ private $actionLevel; + /** + * @param int|string $actionLevel Level or name or value + * + * @phpstan-param Level|LevelName|LogLevel::* $actionLevel + */ public function __construct($actionLevel) { $this->actionLevel = Logger::toMonologLevel($actionLevel); } - public function isHandlerActivated(array $record) + public function isHandlerActivated(array $record): bool { return $record['level'] >= $this->actionLevel; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php index cdabc4458..dfcb3af28 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class FingersCrossedHandler extends AbstractHandler +class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + + /** + * @var callable|HandlerInterface + * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface + */ protected $handler; + /** @var ActivationStrategyInterface */ protected $activationStrategy; + /** @var bool */ protected $buffering = true; + /** @var int */ protected $bufferSize; - protected $buffer = array(); + /** @var Record[] */ + protected $buffer = []; + /** @var bool */ protected $stopBuffering; + /** + * @var ?int + * @phpstan-var ?Level + */ protected $passthruLevel; + /** @var bool */ + protected $bubble; /** - * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $fingersCrossedHandler). - * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action - * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. - * @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 $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $fingersCrossedHandler). + * @param int|string|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @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, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null) + public function __construct($handler, $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, $passthruLevel = null) { if (null === $activationStrategy) { $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); @@ -74,9 +105,9 @@ public function __construct($handler, $activationStrategy = null, $bufferSize = } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return true; } @@ -84,24 +115,24 @@ public function isHandling(array $record) /** * Manually activate this logger regardless of the activation strategy */ - public function activate() + public function activate(): void { if ($this->stopBuffering) { $this->buffering = false; } + $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); - $this->buffer = array(); + $this->buffer = []; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } if ($this->buffering) { @@ -120,18 +151,20 @@ public function handle(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { $this->flushBuffer(); + + $this->getHandler()->close(); } public function reset() { $this->flushBuffer(); - parent::reset(); + $this->resetProcessors(); if ($this->getHandler() instanceof ResettableInterface) { $this->getHandler()->reset(); @@ -143,16 +176,16 @@ public function reset() * * It also resets the handler to its initial buffering state. */ - public function clear() + public function clear(): void { - $this->buffer = array(); + $this->buffer = []; $this->reset(); } /** * Resets the state of the handler. Stops forwarding records to the wrapped handler. */ - private function flushBuffer() + private function flushBuffer(): void { if (null !== $this->passthruLevel) { $level = $this->passthruLevel; @@ -160,11 +193,11 @@ private function flushBuffer() return $record['level'] >= $level; }); if (count($this->buffer) > 0) { - $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); + $this->getHandler(end($this->buffer))->handleBatch($this->buffer); } } - $this->buffer = array(); + $this->buffer = []; $this->buffering = true; } @@ -174,11 +207,13 @@ private function flushBuffer() * If the handler was provided as a factory callable, this will trigger the handler's instantiation. * * @return HandlerInterface + * + * @phpstan-param Record $record */ - public function getHandler(array $record = null) + public function getHandler(?array $record = null) { if (!$this->handler instanceof HandlerInterface) { - $this->handler = call_user_func($this->handler, $record, $this); + $this->handler = ($this->handler)($record, $this); if (!$this->handler instanceof HandlerInterface) { throw new \RuntimeException("The factory callable should return a HandlerInterface"); } @@ -188,20 +223,30 @@ public function getHandler(array $record = null) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->getHandler()->setFormatter($formatter); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->getHandler()->getFormatter(); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php index 2a171bd82..72718de63 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class FirePHPHandler extends AbstractProcessingHandler { + use WebRequestRecognizerTrait; + /** * WildFire JSON header message format */ - const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; /** * FirePHP structure for parsing messages & their presentation */ - const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; /** * Must reference a "known" plugin, otherwise headers won't display in FirePHP */ - const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; /** * Header prefix for Wildfire to recognize & parse headers */ - const HEADER_PREFIX = 'X-Wf'; + protected const HEADER_PREFIX = 'X-Wf'; /** * Whether or not Wildfire vendor-specific headers have been generated & sent yet + * @var bool */ protected static $initialized = false; @@ -51,35 +57,43 @@ class FirePHPHandler extends AbstractProcessingHandler */ protected static $messageIndex = 1; + /** @var bool */ protected static $sendHeaders = true; /** * Base header creation function used by init headers & record headers * - * @param array $meta Wildfire Plugin, Protocol & Structure Indexes - * @param string $message Log message - * @return array Complete header string ready for the client as key and message as value + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * + * @return array Complete header string ready for the client as key and message as value + * + * @phpstan-return non-empty-array */ - protected function createHeader(array $meta, $message) + protected function createHeader(array $meta, string $message): array { - $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta)); - return array($header => $message); + return [$header => $message]; } /** * Creates message header from record * + * @return array + * + * @phpstan-return non-empty-array + * * @see createHeader() - * @param array $record - * @return array + * + * @phpstan-param FormattedRecord $record */ - protected function createRecordHeader(array $record) + protected function createRecordHeader(array $record): array { // Wildfire is extensible to support multiple protocols & plugins in a single request, // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. return $this->createHeader( - array(1, 1, 1, self::$messageIndex++), + [1, 1, 1, self::$messageIndex++], $record['formatted'] ); } @@ -87,7 +101,7 @@ protected function createRecordHeader(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new WildfireFormatter(); } @@ -97,25 +111,23 @@ protected function getDefaultFormatter() * * @see createHeader() * @see sendHeader() - * @return array + * + * @return array */ - protected function getInitHeaders() + protected function getInitHeaders(): array { // Initial payload consists of required headers for Wildfire return array_merge( - $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), - $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), - $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + $this->createHeader(['Protocol', 1], static::PROTOCOL_URI), + $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI), + $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI) ); } /** * Send header string to the client - * - * @param string $header - * @param string $content */ - protected function sendHeader($header, $content) + protected function sendHeader(string $header, string $content): void { if (!headers_sent() && self::$sendHeaders) { header(sprintf('%s: %s', $header, $content)); @@ -127,11 +139,10 @@ protected function sendHeader($header, $content) * * @see sendHeader() * @see sendInitHeaders() - * @param array $record */ - protected function write(array $record) + protected function write(array $record): void { - if (!self::$sendHeaders) { + if (!self::$sendHeaders || !$this->isWebRequest()) { return; } @@ -157,10 +168,8 @@ protected function write(array $record) /** * Verifies if the headers are accepted by the current user agent - * - * @return bool */ - protected function headersAccepted() + protected function headersAccepted(): bool { if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { return true; @@ -168,28 +177,4 @@ protected function headersAccepted() return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); } - - /** - * BC getter for the sendHeaders property that has been made static - */ - public function __get($property) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - return static::$sendHeaders; - } - - /** - * BC setter for the sendHeaders property that has been made static - */ - public function __set($property, $value) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - static::$sendHeaders = $value; - } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php index c43c0134f..85c95b9d7 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class FleepHookHandler extends SocketHandler { - const FLEEP_HOST = 'fleep.io'; + protected const FLEEP_HOST = 'fleep.io'; - const FLEEP_HOOK_URI = '/hook/'; + protected const FLEEP_HOOK_URI = '/hook/'; /** * @var string Webhook token (specifies the conversation where logs are sent) @@ -40,20 +43,35 @@ class FleepHookHandler extends SocketHandler * see https://fleep.io/integrations/webhooks/ * * @param string $token Webhook token - * @param bool|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($token, $level = Logger::DEBUG, $bubble = true) - { + public function __construct( + string $token, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { if (!extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); } $this->token = $token; - $connectionString = 'ssl://' . self::FLEEP_HOST . ':443'; - parent::__construct($connectionString, $level, $bubble); + $connectionString = 'ssl://' . static::FLEEP_HOST . ':443'; + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); } /** @@ -63,29 +81,24 @@ public function __construct($token, $level = Logger::DEBUG, $bubble = true) * * @return LineFormatter */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter(null, null, true, true); } /** * Handles a log record - * - * @param array $record */ - public function write(array $record) + public function write(array $record): void { parent::write($record); $this->closeSocket(); } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); @@ -94,14 +107,11 @@ protected function generateDataStream($record) /** * Builds the header of the API Call - * - * @param string $content - * @return string */ - private function buildHeader($content) + private function buildHeader(string $content): string { - $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; - $header .= "Host: " . self::FLEEP_HOST . "\r\n"; + $header = "POST " . static::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . static::FLEEP_HOST . "\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Content-Length: " . strlen($content) . "\r\n"; $header .= "\r\n"; @@ -112,14 +122,13 @@ private function buildHeader($content) /** * Builds the body of API call * - * @param array $record - * @return string + * @phpstan-param FormattedRecord $record */ - private function buildContent($record) + private function buildContent(array $record): string { - $dataArray = array( + $dataArray = [ 'message' => $record['formatted'], - ); + ]; return http_build_query($dataArray); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php index f0f010cbf..5715d5800 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -1,4 +1,4 @@ - * @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 { @@ -35,26 +38,39 @@ class FlowdockHandler extends SocketHandler protected $apiToken; /** - * @param string $apiToken - * @param bool|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($apiToken, $level = Logger::DEBUG, $bubble = true) - { + public function __construct( + string $apiToken, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { if (!extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); } - parent::__construct('ssl://api.flowdock.com:443', $level, $bubble); + parent::__construct( + 'ssl://api.flowdock.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); $this->apiToken = $apiToken; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { if (!$formatter instanceof FlowdockFormatter) { throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); @@ -65,20 +81,16 @@ public function setFormatter(FormatterInterface $formatter) /** * Gets the default formatter. - * - * @return FormatterInterface */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); } /** - * {@inheritdoc} - * - * @param array $record + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { parent::write($record); @@ -86,12 +98,9 @@ protected function write(array $record) } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); @@ -101,21 +110,17 @@ protected function generateDataStream($record) /** * Builds the body of API call * - * @param array $record - * @return string + * @phpstan-param FormattedRecord $record */ - private function buildContent($record) + private function buildContent(array $record): string { return Utils::jsonEncode($record['formatted']['flowdock']); } /** * Builds the header of the API Call - * - * @param string $content - * @return string */ - private function buildHeader($content) + private function buildHeader(string $content): string { $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; $header .= "Host: api.flowdock.com\r\n"; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php index 3e2f1b28a..fc1693cd0 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php @@ -16,8 +16,6 @@ /** * Interface to describe loggers that have a formatter * - * This interface is present in monolog 1.x to ease forward compatibility. - * * @author Jordi Boggiano */ interface FormattableHandlerInterface diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php index e9ec5e776..b60bdce0e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php @@ -17,20 +17,17 @@ /** * Helper trait for implementing FormattableInterface * - * This trait is present in monolog 1.x to ease forward compatibility. - * * @author Jordi Boggiano */ trait FormattableHandlerTrait { /** - * @var FormatterInterface + * @var ?FormatterInterface */ protected $formatter; /** - * {@inheritdoc} - * @suppress PhanTypeMismatchReturn + * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter): HandlerInterface { @@ -40,7 +37,7 @@ public function setFormatter(FormatterInterface $formatter): HandlerInterface } /** - * {@inheritdoc} + * {@inheritDoc} */ public function getFormatter(): FormatterInterface { diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php index b6cde7c65..4ff26c4cd 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -1,4 +1,4 @@ -publisher = $publisher; } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->publisher->publish($record['formatted']); } @@ -58,7 +50,7 @@ protected function write(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new GelfMessageFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php index 0d461f9c7..3c9dc4b3b 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ -class GroupHandler extends AbstractHandler +class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface { + use ProcessableHandlerTrait; + + /** @var HandlerInterface[] */ protected $handlers; + /** @var bool */ + protected $bubble; /** - * @param array $handlers Array of Handlers. - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param HandlerInterface[] $handlers Array of Handlers. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct(array $handlers, $bubble = true) + public function __construct(array $handlers, bool $bubble = true) { foreach ($handlers as $handler) { if (!$handler instanceof HandlerInterface) { @@ -40,9 +47,9 @@ public function __construct(array $handlers, $bubble = true) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { foreach ($this->handlers as $handler) { if ($handler->isHandling($record)) { @@ -54,14 +61,13 @@ public function isHandling(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { @@ -72,18 +78,16 @@ public function handle(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { if ($this->processors) { - $processed = array(); + $processed = []; foreach ($records as $record) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } - $processed[] = $record; + $processed[] = $this->processRecord($record); } + /** @var Record[] $records */ $records = $processed; } @@ -94,7 +98,7 @@ public function handleBatch(array $records) public function reset() { - parent::reset(); + $this->resetProcessors(); foreach ($this->handlers as $handler) { if ($handler instanceof ResettableInterface) { @@ -103,13 +107,24 @@ public function reset() } } + public function close(): void + { + parent::close(); + + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { foreach ($this->handlers as $handler) { - $handler->setFormatter($formatter); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + } } return $this; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Handler.php b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php new file mode 100644 index 000000000..34b4935dd --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing basic close() support as well as handleBatch + * + * @author Jordi Boggiano + */ +abstract class Handler implements HandlerInterface +{ + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable $e) { + // do nothing + } + } + + public function __sleep() + { + $this->close(); + + $reflClass = new \ReflectionClass($this); + + $keys = []; + foreach ($reflClass->getProperties() as $reflProp) { + if (!$reflProp->isStatic()) { + $keys[] = $reflProp->getName(); + } + } + + return $keys; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php index 8d5a4a095..affcc51fc 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ interface HandlerInterface { @@ -32,8 +33,10 @@ interface HandlerInterface * @param array $record Partial log record containing only a level key * * @return bool + * + * @phpstan-param array{level: Level} $record */ - public function isHandling(array $record); + public function isHandling(array $record): bool; /** * Handles a record. @@ -45,46 +48,38 @@ public function isHandling(array $record); * Unless the bubbling is interrupted (by returning true), the Logger class will keep on * calling further handlers in the stack with a given log record. * - * @param array $record The record to handle - * @return bool true means that this handler handled the record, and that bubbling is not permitted. - * false means the record was either not processed or that this handler allows bubbling. + * @param array $record The record to handle + * @return bool true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + * + * @phpstan-param Record $record */ - public function handle(array $record); + public function handle(array $record): bool; /** * Handles a set of records at once. * * @param array $records The records to handle (an array of record arrays) - */ - public function handleBatch(array $records); - - /** - * Adds a processor in the stack. * - * @param callable $callback - * @return self + * @phpstan-param Record[] $records */ - public function pushProcessor($callback); + public function handleBatch(array $records): void; /** - * Removes the processor on top of the stack and returns it. + * Closes the handler. * - * @return callable - */ - public function popProcessor(); - - /** - * Sets the formatter. + * Ends a log cycle and frees all resources used by the handler. * - * @param FormatterInterface $formatter - * @return self - */ - public function setFormatter(FormatterInterface $formatter); - - /** - * Gets the formatter. + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * + * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage) + * and ideally handlers should be able to reopen themselves on handle() after they have been closed. + * + * This is useful at the end of a request and will be called automatically when the object + * is destroyed if you extend Monolog\Handler\Handler. * - * @return FormatterInterface + * If you are thinking of calling this method yourself, most likely you should be + * calling ResettableInterface::reset instead. Have a look. */ - public function getFormatter(); + public function close(): void; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php index 55e649868..d4351b9f9 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php @@ -1,4 +1,4 @@ - */ -class HandlerWrapper implements HandlerInterface, ResettableInterface +class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface { /** * @var HandlerInterface */ protected $handler; - /** - * HandlerWrapper constructor. - * @param HandlerInterface $handler - */ public function __construct(HandlerInterface $handler) { $this->handler = $handler; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return $this->handler->isHandling($record); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { return $this->handler->handle($record); } /** - * {@inheritdoc} + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $this->handler->handleBatch($records); + } + + /** + * {@inheritDoc} */ - public function handleBatch(array $records) + public function close(): void { - return $this->handler->handleBatch($records); + $this->handler->close(); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function pushProcessor($callback) + public function pushProcessor(callable $callback): HandlerInterface { - $this->handler->pushProcessor($callback); + if ($this->handler instanceof ProcessableHandlerInterface) { + $this->handler->pushProcessor($callback); - return $this; + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function popProcessor() + public function popProcessor(): callable { - return $this->handler->popProcessor(); + if ($this->handler instanceof ProcessableHandlerInterface) { + return $this->handler->popProcessor(); + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->handler->setFormatter($formatter); + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->handler->getFormatter(); + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); } public function reset() { if ($this->handler instanceof ResettableInterface) { - return $this->handler->reset(); + $this->handler->reset(); } } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php deleted file mode 100644 index 30258e36e..000000000 --- a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php +++ /dev/null @@ -1,367 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Logger; - -/** - * Sends notifications through the hipchat api to a hipchat room - * - * Notes: - * API token - HipChat API token - * Room - HipChat Room Id or name, where messages are sent - * Name - Name used to send the message (from) - * notify - Should the message trigger a notification in the clients - * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) - * - * @author Rafael Dohms - * @see https://www.hipchat.com/docs/api - */ -class HipChatHandler extends SocketHandler -{ - /** - * Use API version 1 - */ - const API_V1 = 'v1'; - - /** - * Use API version v2 - */ - const API_V2 = 'v2'; - - /** - * The maximum allowed length for the name used in the "from" field. - */ - const MAXIMUM_NAME_LENGTH = 15; - - /** - * The maximum allowed length for the message. - */ - const MAXIMUM_MESSAGE_LENGTH = 9500; - - /** - * @var string - */ - private $token; - - /** - * @var string - */ - private $room; - - /** - * @var string - */ - private $name; - - /** - * @var bool - */ - private $notify; - - /** - * @var string - */ - private $format; - - /** - * @var string - */ - private $host; - - /** - * @var string - */ - private $version; - - /** - * @param string $token HipChat API Token - * @param string $room The room that should be alerted of the message (Id or Name) - * @param string $name Name used in the "from" field. - * @param bool $notify Trigger a notification in clients or not - * @param 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. - * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) - * @param string $host The HipChat server hostname. - * @param string $version The HipChat API version (default HipChatHandler::API_V1) - */ - public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) - { - @trigger_error('The Monolog\Handler\HipChatHandler class is deprecated. You should migrate to Slack and the SlackWebhookHandler / SlackbotHandler, see https://www.atlassian.com/partnerships/slack', E_USER_DEPRECATED); - - if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { - throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); - } - - $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; - parent::__construct($connectionString, $level, $bubble); - - $this->token = $token; - $this->name = $name; - $this->notify = $notify; - $this->room = $room; - $this->format = $format; - $this->host = $host; - $this->version = $version; - } - - /** - * {@inheritdoc} - * - * @param array $record - * @return string - */ - protected function generateDataStream($record) - { - $content = $this->buildContent($record); - - return $this->buildHeader($content) . $content; - } - - /** - * Builds the body of API call - * - * @param array $record - * @return string - */ - private function buildContent($record) - { - $dataArray = array( - 'notify' => $this->version == self::API_V1 ? - ($this->notify ? 1 : 0) : - ($this->notify ? 'true' : 'false'), - 'message' => $record['formatted'], - 'message_format' => $this->format, - 'color' => $this->getAlertColor($record['level']), - ); - - if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) { - if (function_exists('mb_substr')) { - $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; - } else { - $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; - } - } - - // if we are using the legacy API then we need to send some additional information - if ($this->version == self::API_V1) { - $dataArray['room_id'] = $this->room; - } - - // append the sender name if it is set - // always append it if we use the v1 api (it is required in v1) - if ($this->version == self::API_V1 || $this->name !== null) { - $dataArray['from'] = (string) $this->name; - } - - return http_build_query($dataArray); - } - - /** - * Builds the header of the API Call - * - * @param string $content - * @return string - */ - private function buildHeader($content) - { - if ($this->version == self::API_V1) { - $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; - } else { - // needed for rooms with special (spaces, etc) characters in the name - $room = rawurlencode($this->room); - $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; - } - - $header .= "Host: {$this->host}\r\n"; - $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; - $header .= "Content-Length: " . strlen($content) . "\r\n"; - $header .= "\r\n"; - - return $header; - } - - /** - * Assigns a color to each level of log records. - * - * @param int $level - * @return string - */ - protected function getAlertColor($level) - { - switch (true) { - case $level >= Logger::ERROR: - return 'red'; - case $level >= Logger::WARNING: - return 'yellow'; - case $level >= Logger::INFO: - return 'green'; - case $level == Logger::DEBUG: - return 'gray'; - default: - return 'yellow'; - } - } - - /** - * {@inheritdoc} - * - * @param array $record - */ - protected function write(array $record) - { - parent::write($record); - $this->finalizeWrite(); - } - - /** - * Finalizes the request by reading some bytes and then closing the socket - * - * If we do not read some but close the socket too early, hipchat sometimes - * drops the request entirely. - */ - protected function finalizeWrite() - { - $res = $this->getResource(); - if (is_resource($res)) { - @fread($res, 2048); - } - $this->closeSocket(); - } - - /** - * {@inheritdoc} - */ - public function handleBatch(array $records) - { - if (count($records) == 0) { - return true; - } - - $batchRecords = $this->combineRecords($records); - - $handled = false; - foreach ($batchRecords as $batchRecord) { - if ($this->isHandling($batchRecord)) { - $this->write($batchRecord); - $handled = true; - } - } - - if (!$handled) { - return false; - } - - return false === $this->bubble; - } - - /** - * Combines multiple records into one. Error level of the combined record - * will be the highest level from the given records. Datetime will be taken - * from the first record. - * - * @param array $records - * @return array - */ - private function combineRecords(array $records) - { - $batchRecord = null; - $batchRecords = array(); - $messages = array(); - $formattedMessages = array(); - $level = 0; - $levelName = null; - $datetime = null; - - foreach ($records as $record) { - $record = $this->processRecord($record); - - if ($record['level'] > $level) { - $level = $record['level']; - $levelName = $record['level_name']; - } - - if (null === $datetime) { - $datetime = $record['datetime']; - } - - $messages[] = $record['message']; - $messageStr = implode(PHP_EOL, $messages); - $formattedMessages[] = $this->getFormatter()->format($record); - $formattedMessageStr = implode('', $formattedMessages); - - $batchRecord = array( - 'message' => $messageStr, - 'formatted' => $formattedMessageStr, - 'context' => array(), - 'extra' => array(), - ); - - if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { - // Pop the last message and implode the remaining messages - $lastMessage = array_pop($messages); - $lastFormattedMessage = array_pop($formattedMessages); - $batchRecord['message'] = implode(PHP_EOL, $messages); - $batchRecord['formatted'] = implode('', $formattedMessages); - - $batchRecords[] = $batchRecord; - $messages = array($lastMessage); - $formattedMessages = array($lastFormattedMessage); - - $batchRecord = null; - } - } - - if (null !== $batchRecord) { - $batchRecords[] = $batchRecord; - } - - // Set the max level and datetime for all records - foreach ($batchRecords as &$batchRecord) { - $batchRecord = array_merge( - $batchRecord, - array( - 'level' => $level, - 'level_name' => $levelName, - 'datetime' => $datetime, - ) - ); - } - - return $batchRecords; - } - - /** - * Validates the length of a string. - * - * If the `mb_strlen()` function is available, it will use that, as HipChat - * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`. - * - * Note that this might cause false failures in the specific case of using - * a valid name with less than 16 characters, but 16 or more bytes, on a - * system where `mb_strlen()` is unavailable. - * - * @param string $str - * @param int $length - * - * @return bool - */ - private function validateStringLength($str, $length) - { - if (function_exists('mb_strlen')) { - return (mb_strlen($str) <= $length); - } - - return (strlen($str) <= $length); - } -} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php index f4d3b97eb..000ccea40 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php @@ -1,4 +1,4 @@ -eventName = $eventName; $this->secretKey = $secretKey; @@ -45,15 +49,15 @@ public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bub } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function write(array $record) + public function write(array $record): void { - $postData = array( + $postData = [ "value1" => $record["channel"], "value2" => $record["level_name"], "value3" => $record["message"], - ); + ]; $postString = Utils::jsonEncode($postData); $ch = curl_init(); @@ -61,9 +65,9 @@ public function write(array $record) curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); - curl_setopt($ch, CURLOPT_HTTPHEADER, array( + curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", - )); + ]); Curl\Util::execute($ch); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php index 8f683dce5..71f64a267 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php @@ -1,4 +1,4 @@ -logToken = $token; } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { return $this->logToken . ' ' . $record['formatted']; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php index ea89fb3ed..25fcd1594 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -1,4 +1,4 @@ -logToken = $token; } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { return $this->logToken . ' ' . $record['formatted']; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php index bcd62e1c5..6d13db375 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -1,4 +1,4 @@ -token = $token; @@ -42,26 +59,72 @@ public function __construct($token, $level = Logger::DEBUG, $bubble = true) parent::__construct($level, $bubble); } - public function setTag($tag) + /** + * Loads and returns the shared curl handler for the given endpoint. + * + * @param string $endpoint + * + * @return resource|CurlHandle + */ + protected function getCurlHandler(string $endpoint) { - $tag = !empty($tag) ? $tag : array(); - $this->tag = is_array($tag) ? $tag : array($tag); + if (!array_key_exists($endpoint, $this->curlHandlers)) { + $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint); + } + + return $this->curlHandlers[$endpoint]; } - public function addTag($tag) + /** + * Starts a fresh curl session for the given endpoint and returns its handler. + * + * @param string $endpoint + * + * @return resource|CurlHandle + */ + private function loadCurlHandle(string $endpoint) + { + $url = sprintf("https://%s/%s/%s/", static::HOST, $endpoint, $this->token); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + return $ch; + } + + /** + * @param string[]|string $tag + */ + public function setTag($tag): self + { + $tag = !empty($tag) ? $tag : []; + $this->tag = is_array($tag) ? $tag : [$tag]; + + return $this; + } + + /** + * @param string[]|string $tag + */ + public function addTag($tag): self { if (!empty($tag)) { - $tag = is_array($tag) ? $tag : array($tag); + $tag = is_array($tag) ? $tag : [$tag]; $this->tag = array_unique(array_merge($this->tag, $tag)); } + + return $this; } - protected function write(array $record) + protected function write(array $record): void { - $this->send($record["formatted"], self::ENDPOINT_SINGLE); + $this->send($record["formatted"], static::ENDPOINT_SINGLE); } - public function handleBatch(array $records) + public function handleBatch(array $records): void { $level = $this->level; @@ -70,32 +133,27 @@ public function handleBatch(array $records) }); if ($records) { - $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH); + $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH); } } - protected function send($data, $endpoint) + protected function send(string $data, string $endpoint): void { - $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token); + $ch = $this->getCurlHandler($endpoint); - $headers = array('Content-Type: application/json'); + $headers = ['Content-Type: application/json']; if (!empty($this->tag)) { $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); } - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - Curl\Util::execute($ch); + Curl\Util::execute($ch, 5, false); } - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LogglyFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php new file mode 100644 index 000000000..859a46906 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogmaticFormatter; + +/** + * @author Julien Breux + */ +class LogmaticHandler extends SocketHandler +{ + /** + * @var string + */ + private $logToken; + + /** + * @var string + */ + private $hostname; + + /** + * @var string + */ + private $appname; + + /** + * @param string $token Log token supplied by Logmatic. + * @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. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + string $hostname = '', + string $appname = '', + bool $useSSL = true, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler'); + } + + $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514'; + $endpoint .= '/v1/'; + + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->logToken = $token; + $this->hostname = $hostname; + $this->appname = $appname; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + return $this->logToken . ' ' . $record['formatted']; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + $formatter = new LogmaticFormatter(); + + if (!empty($this->hostname)) { + $formatter->setHostname($this->hostname); + } + if (!empty($this->appname)) { + $formatter->setAppname($this->appname); + } + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php index 9e2328385..97f343202 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -1,4 +1,4 @@ -level) { continue; } - $messages[] = $this->processRecord($record); + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; } if (!empty($messages)) { @@ -42,18 +49,24 @@ public function handleBatch(array $records) * * @param string $content formatted email body to be sent * @param array $records the array of log records that formed this content + * + * @phpstan-param Record[] $records */ - abstract protected function send($content, array $records); + abstract protected function send(string $content, array $records): void; /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { - $this->send((string) $record['formatted'], array($record)); + $this->send((string) $record['formatted'], [$record]); } - protected function getHighestRecord(array $records) + /** + * @phpstan-param non-empty-array $records + * @phpstan-return Record + */ + protected function getHighestRecord(array $records): array { $highestRecord = null; foreach ($records as $record) { @@ -64,4 +77,19 @@ protected function getHighestRecord(array $records) return $highestRecord; } + + protected function isHtmlBody(string $body): bool + { + return ($body[0] ?? null) === '<'; + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new HtmlFormatter(); + } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php index de039a869..3003500ec 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -1,4 +1,4 @@ -message = $message; @@ -44,15 +48,22 @@ public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records): void { + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + $message = clone $this->message; - $message->setBody($content); - if (version_compare(\Swift::VERSION, '6.0.0', '>=')) { + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { $message->setDate(new \DateTimeImmutable()); } else { + /** @phpstan-ignore-next-line */ $message->setDate(time()); } @@ -61,11 +72,11 @@ protected function send($content, array $records) curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ 'key' => $this->apiKey, 'raw_message' => (string) $message, 'async' => false, - ))); + ])); Curl\Util::execute($ch); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php index 4724a7e2d..3965aeea5 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -1,4 +1,4 @@ - + * @author Christian Bergau */ class MissingExtensionException extends \Exception { diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php index 56fe755b9..150efeeac 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -1,4 +1,4 @@ -pushHandler($mongodb); * - * @author Thomas Tourlourat + * The above examples uses the MongoDB PHP library's client class; however, the + * MongoDB\Driver\Manager class from ext-mongodb is also supported. */ class MongoDBHandler extends AbstractProcessingHandler { - protected $mongoCollection; + /** @var Collection */ + private $collection; + /** @var Client|Manager */ + private $manager; + /** @var string|null */ + private $namespace = null; - public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + /** + * Constructor. + * + * @param Client|Manager $mongodb MongoDB library or driver client + * @param string $database Database name + * @param string $collection Collection name + */ + public function __construct($mongodb, string $database, string $collection, $level = Logger::DEBUG, bool $bubble = true) { - if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) { - throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required'); + if (!($mongodb instanceof Client || $mongodb instanceof Manager)) { + throw new \InvalidArgumentException('MongoDB\Client or MongoDB\Driver\Manager instance required'); } - $this->mongoCollection = $mongo->selectCollection($database, $collection); + if ($mongodb instanceof Client) { + $this->collection = method_exists($mongodb, 'getCollection') ? $mongodb->getCollection($database, $collection) : $mongodb->selectCollection($database, $collection); + } else { + $this->manager = $mongodb; + $this->namespace = $database . '.' . $collection; + } parent::__construct($level, $bubble); } - protected function write(array $record) + protected function write(array $record): void { - if ($this->mongoCollection instanceof \MongoDB\Collection) { - $this->mongoCollection->insertOne($record["formatted"]); - } else { - $this->mongoCollection->save($record["formatted"]); + if (isset($this->collection)) { + $this->collection->insertOne($record['formatted']); + } + + if (isset($this->manager, $this->namespace)) { + $bulk = new BulkWrite; + $bulk->insert($record["formatted"]); + $this->manager->executeBulkWrite($this->namespace, $bulk); } } /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { - return new NormalizerFormatter(); + return new MongoDBFormatter; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php index d7807fd11..0c0a3bdb1 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -1,4 +1,4 @@ -to = is_array($to) ? $to : array($to); + $this->to = (array) $to; $this->subject = $subject; $this->addHeader(sprintf('From: %s', $from)); $this->maxColumnWidth = $maxColumnWidth; @@ -84,10 +82,9 @@ public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubbl /** * Add headers to the message * - * @param string|array $headers Custom added headers - * @return self + * @param string|string[] $headers Custom added headers */ - public function addHeader($headers) + public function addHeader($headers): self { foreach ((array) $headers as $header) { if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { @@ -102,10 +99,9 @@ public function addHeader($headers) /** * Add parameters to the message * - * @param string|array $parameters Custom added parameters - * @return self + * @param string|string[] $parameters Custom added parameters */ - public function addParameter($parameters) + public function addParameter($parameters): self { $this->parameters = array_merge($this->parameters, (array) $parameters); @@ -113,14 +109,19 @@ public function addParameter($parameters) } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records): void { - $content = wordwrap($content, $this->maxColumnWidth); + $contentType = $this->getContentType() ?: ($this->isHtmlBody($content) ? 'text/html' : 'text/plain'); + + if ($contentType !== 'text/html') { + $content = wordwrap($content, $this->maxColumnWidth); + } + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); - $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n"; - if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n"; + if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) { $headers .= 'MIME-Version: 1.0' . "\r\n"; } @@ -136,28 +137,20 @@ protected function send($content, array $records) } } - /** - * @return string $contentType - */ - public function getContentType() + public function getContentType(): ?string { return $this->contentType; } - /** - * @return string $encoding - */ - public function getEncoding() + public function getEncoding(): string { return $this->encoding; } /** - * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML - * messages. - * @return self + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages. */ - public function setContentType($contentType) + public function setContentType(string $contentType): self { if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); @@ -168,11 +161,7 @@ public function setContentType($contentType) return $this; } - /** - * @param string $encoding - * @return self - */ - public function setEncoding($encoding) + public function setEncoding(string $encoding): self { if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php index 64dc1381a..114d749eb 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -1,4 +1,4 @@ -isNewRelicEnabled()) { throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); @@ -87,7 +88,7 @@ protected function write(array $record) unset($record['formatted']['context']['transaction_name']); } - if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { newrelic_notice_error($record['message'], $record['context']['exception']); unset($record['formatted']['context']['exception']); } else { @@ -124,7 +125,7 @@ protected function write(array $record) * * @return bool */ - protected function isNewRelicEnabled() + protected function isNewRelicEnabled(): bool { return extension_loaded('newrelic'); } @@ -133,10 +134,9 @@ protected function isNewRelicEnabled() * Returns the appname where this log should be sent. Each log can override the default appname, set in this * handler's constructor, by providing the appname in it's context. * - * @param array $context - * @return null|string + * @param mixed[] $context */ - protected function getAppName(array $context) + protected function getAppName(array $context): ?string { if (isset($context['appname'])) { return $context['appname']; @@ -149,11 +149,9 @@ protected function getAppName(array $context) * Returns the name of the current transaction. Each log can override the default transaction name, set in this * handler's constructor, by providing the transaction_name in it's context * - * @param array $context - * - * @return null|string + * @param mixed[] $context */ - protected function getTransactionName(array $context) + protected function getTransactionName(array $context): ?string { if (isset($context['transaction_name'])) { return $context['transaction_name']; @@ -164,20 +162,16 @@ protected function getTransactionName(array $context) /** * Sets the NewRelic application that should receive this log. - * - * @param string $appName */ - protected function setNewRelicAppName($appName) + protected function setNewRelicAppName(string $appName): void { newrelic_set_appname($appName); } /** * Overwrites the name of the current transaction - * - * @param string $transactionName */ - protected function setNewRelicTransactionName($transactionName) + protected function setNewRelicTransactionName(string $transactionName): void { newrelic_name_transaction($transactionName); } @@ -186,7 +180,7 @@ protected function setNewRelicTransactionName($transactionName) * @param string $key * @param mixed $value */ - protected function setNewRelicParameter($key, $value) + protected function setNewRelicParameter(string $key, $value): void { if (null === $value || is_scalar($value)) { newrelic_add_custom_parameter($key, $value); @@ -198,7 +192,7 @@ protected function setNewRelicParameter($key, $value) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php new file mode 100644 index 000000000..1ddf0beb9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * No-op + * + * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack. + * This can be used for testing, or to disable a handler when overriding a configuration without + * influencing the rest of the stack. + * + * @author Roel Harbers + */ +class NoopHandler extends Handler +{ + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return true; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php index 4b8458833..e75ee0c6e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class NullHandler extends AbstractHandler +class NullHandler extends Handler { /** - * @param int $level The minimum logging level at which this handler will be triggered + * @var int + */ + private $level; + + /** + * @param string|int $level The minimum logging level at which this handler will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ public function __construct($level = Logger::DEBUG) { - parent::__construct($level, false); + $this->level = Logger::toMonologLevel($level); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function isHandling(array $record): bool { - if ($record['level'] < $this->level) { - return false; - } + return $record['level'] >= $this->level; + } - return true; + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return $record['level'] >= $this->level; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php new file mode 100644 index 000000000..22068c9a3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; + +/** + * Handler to only pass log messages when a certain threshold of number of messages is reached. + * + * This can be useful in cases of processing a batch of data, but you're for example only interested + * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right? + * + * Usage example: + * + * ``` + * $log = new Logger('application'); + * $handler = new SomeHandler(...) + * + * // Pass all warnings to the handler when more than 10 & all error messages when more then 5 + * $overflow = new OverflowHandler($handler, [Logger::WARNING => 10, Logger::ERROR => 5]); + * + * $log->pushHandler($overflow); + *``` + * + * @author Kris Buist + */ +class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface +{ + /** @var HandlerInterface */ + private $handler; + + /** @var int[] */ + private $thresholdMap = [ + Logger::DEBUG => 0, + Logger::INFO => 0, + Logger::NOTICE => 0, + Logger::WARNING => 0, + Logger::ERROR => 0, + Logger::CRITICAL => 0, + Logger::ALERT => 0, + Logger::EMERGENCY => 0, + ]; + + /** + * Buffer of all messages passed to the handler before the threshold was reached + * + * @var mixed[][] + */ + private $buffer = []; + + /** + * @param HandlerInterface $handler + * @param int[] $thresholdMap Dictionary of logger level => threshold + */ + public function __construct( + HandlerInterface $handler, + array $thresholdMap = [], + $level = Logger::DEBUG, + bool $bubble = true + ) { + $this->handler = $handler; + foreach ($thresholdMap as $thresholdLevel => $threshold) { + $this->thresholdMap[$thresholdLevel] = $threshold; + } + parent::__construct($level, $bubble); + } + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($record['level'] < $this->level) { + return false; + } + + $level = $record['level']; + + if (!isset($this->thresholdMap[$level])) { + $this->thresholdMap[$level] = 0; + } + + if ($this->thresholdMap[$level] > 0) { + // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1 + $this->thresholdMap[$level]--; + $this->buffer[$level][] = $record; + + return false === $this->bubble; + } + + if ($this->thresholdMap[$level] == 0) { + // This current message is breaking the threshold. Flush the buffer and continue handling the current record + foreach ($this->buffer[$level] ?? [] as $buffered) { + $this->handler->handle($buffered); + } + $this->thresholdMap[$level]--; + unset($this->buffer[$level]); + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php index d0a8b43e7..23a1d1178 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -1,4 +1,4 @@ -addDebug('SELECT * FROM users', array('db', 'time' => 0.012)); + * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012)); * PC::debug($_SERVER); // PHP Console debugger for any type of vars * * @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 { - private $options = array( + /** @var array */ + private $options = [ 'enabled' => true, // bool Is PHP Console server enabled - 'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with... - 'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with... + 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled 'useOwnErrorsHandler' => false, // bool Enable errors handling 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths @@ -52,7 +56,7 @@ class PHPConsoleHandler extends AbstractProcessingHandler 'headersLimit' => null, // int|null Set headers size limit for your web-server 'password' => null, // string|null Protect PHP Console connection by password 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed - 'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level @@ -60,40 +64,43 @@ class PHPConsoleHandler extends AbstractProcessingHandler 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug - 'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) - ); + 'dataStorage' => null, // \PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ]; /** @var Connector */ private $connector; /** - * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details - * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) - * @param int $level - * @param bool $bubble - * @throws Exception + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @throws \RuntimeException */ - public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true) + public function __construct(array $options = [], ?Connector $connector = null, $level = Logger::DEBUG, bool $bubble = true) { if (!class_exists('PhpConsole\Connector')) { - throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); } parent::__construct($level, $bubble); $this->options = $this->initOptions($options); $this->connector = $this->initConnector($connector); } - private function initOptions(array $options) + /** + * @param array $options + * + * @return array + */ + private function initOptions(array $options): array { $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); if ($wrongOptions) { - throw new Exception('Unknown options: ' . implode(', ', $wrongOptions)); + throw new \RuntimeException('Unknown options: ' . implode(', ', $wrongOptions)); } return array_replace($this->options, $options); } - private function initConnector(Connector $connector = null) + private function initConnector(?Connector $connector = null): Connector { if (!$connector) { if ($this->options['dataStorage']) { @@ -108,7 +115,7 @@ private function initConnector(Connector $connector = null) if ($this->options['enabled'] && $connector->isActiveClient()) { if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { - $handler = Handler::getInstance(); + $handler = VendorPhpConsoleHandler::getInstance(); $handler->setHandleErrors($this->options['useOwnErrorsHandler']); $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); $handler->start(); @@ -148,17 +155,20 @@ private function initConnector(Connector $connector = null) return $connector; } - public function getConnector() + public function getConnector(): Connector { return $this->connector; } - public function getOptions() + /** + * @return array + */ + public function getOptions(): array { return $this->options; } - public function handle(array $record) + public function handle(array $record): bool { if ($this->options['enabled'] && $this->connector->isActiveClient()) { return parent::handle($record); @@ -169,22 +179,22 @@ public function handle(array $record) /** * Writes the record down to the log of the implementing handler - * - * @param array $record - * @return void */ - protected function write(array $record) + protected function write(array $record): void { if ($record['level'] < Logger::NOTICE) { $this->handleDebugRecord($record); - } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { $this->handleExceptionRecord($record); } else { $this->handleErrorRecord($record); } } - private function handleDebugRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function handleDebugRecord(array $record): void { $tags = $this->getRecordTags($record); $message = $record['message']; @@ -194,24 +204,34 @@ private function handleDebugRecord(array $record) $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); } - private function handleExceptionRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function handleExceptionRecord(array $record): void { $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); } - private function handleErrorRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function handleErrorRecord(array $record): void { $context = $record['context']; $this->connector->getErrorsDispatcher()->dispatchError( - isset($context['code']) ? $context['code'] : null, - isset($context['message']) ? $context['message'] : $record['message'], - isset($context['file']) ? $context['file'] : null, - isset($context['line']) ? $context['line'] : null, + $context['code'] ?? null, + $context['message'] ?? $record['message'], + $context['file'] ?? null, + $context['line'] ?? null, $this->options['classesPartialsTraceIgnore'] ); } + /** + * @phpstan-param Record $record + * @return string + */ private function getRecordTags(array &$record) { $tags = null; @@ -236,7 +256,7 @@ private function getRecordTags(array &$record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter('%message%'); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php new file mode 100644 index 000000000..8a8cf1be6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to STDIN of any process, specified by a command. + * + * Usage example: + *
+ * $log = new Logger('myLogger');
+ * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
+ * 
+ * + * @author Kolja Zuelsdorf + */ +class ProcessHandler extends AbstractProcessingHandler +{ + /** + * Holds the process to receive data on its STDIN. + * + * @var resource|bool|null + */ + private $process; + + /** + * @var string + */ + private $command; + + /** + * @var string|null + */ + private $cwd; + + /** + * @var resource[] + */ + private $pipes = []; + + /** + * @var array + */ + protected const DESCRIPTOR_SPEC = [ + 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from + 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to + 2 => ['pipe', 'w'], // STDERR is a pipe to catch the any errors + ]; + + /** + * @param string $command Command for the process to start. Absolute paths are recommended, + * especially if you do not use the $cwd parameter. + * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in. + * @throws \InvalidArgumentException + */ + public function __construct(string $command, $level = Logger::DEBUG, bool $bubble = true, ?string $cwd = null) + { + if ($command === '') { + throw new \InvalidArgumentException('The command argument must be a non-empty string.'); + } + if ($cwd === '') { + throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); + } + + parent::__construct($level, $bubble); + + $this->command = $command; + $this->cwd = $cwd; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @throws \UnexpectedValueException + */ + protected function write(array $record): void + { + $this->ensureProcessIsStarted(); + + $this->writeProcessInput($record['formatted']); + + $errors = $this->readProcessErrors(); + if (empty($errors) === false) { + throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors)); + } + } + + /** + * Makes sure that the process is actually started, and if not, starts it, + * assigns the stream pipes, and handles startup errors, if any. + */ + private function ensureProcessIsStarted(): void + { + if (is_resource($this->process) === false) { + $this->startProcess(); + + $this->handleStartupErrors(); + } + } + + /** + * Starts the actual process and sets all streams to non-blocking. + */ + private function startProcess(): void + { + $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd); + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, false); + } + } + + /** + * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any. + * + * @throws \UnexpectedValueException + */ + private function handleStartupErrors(): void + { + $selected = $this->selectErrorStream(); + if (false === $selected) { + throw new \UnexpectedValueException('Something went wrong while selecting a stream.'); + } + + $errors = $this->readProcessErrors(); + + if (is_resource($this->process) === false || empty($errors) === false) { + throw new \UnexpectedValueException( + sprintf('The process "%s" could not be opened: ' . $errors, $this->command) + ); + } + } + + /** + * Selects the STDERR stream. + * + * @return int|bool + */ + protected function selectErrorStream() + { + $empty = []; + $errorPipes = [$this->pipes[2]]; + + return stream_select($errorPipes, $empty, $empty, 1); + } + + /** + * Reads the errors of the process, if there are any. + * + * @codeCoverageIgnore + * @return string Empty string if there are no errors. + */ + protected function readProcessErrors(): string + { + return (string) stream_get_contents($this->pipes[2]); + } + + /** + * Writes to the input stream of the opened process. + * + * @codeCoverageIgnore + */ + protected function writeProcessInput(string $string): void + { + fwrite($this->pipes[0], $string); + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + if (is_resource($this->process)) { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + proc_close($this->process); + $this->process = null; + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php index 66a3d83ae..3adec7a4d 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php @@ -16,25 +16,29 @@ /** * Interface to describe loggers that have processors * - * This interface is present in monolog 1.x to ease forward compatibility. - * * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger */ interface ProcessableHandlerInterface { /** * Adds a processor in the stack. * + * @psalm-param ProcessorInterface|callable(Record): Record $callback + * * @param ProcessorInterface|callable $callback * @return HandlerInterface self */ - public function pushProcessor($callback): HandlerInterface; + public function pushProcessor(callable $callback): HandlerInterface; /** * Removes the processor on top of the stack and returns it. * - * @throws \LogicException In case the processor stack is empty - * @return callable + * @psalm-return ProcessorInterface|callable(Record): Record $callback + * + * @throws \LogicException In case the processor stack is empty + * @return callable|ProcessorInterface */ public function popProcessor(): callable; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php index 09f32a12c..9ef6e301c 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -12,26 +12,27 @@ namespace Monolog\Handler; use Monolog\ResettableInterface; +use Monolog\Processor\ProcessorInterface; /** * Helper trait for implementing ProcessableInterface * - * This trait is present in monolog 1.x to ease forward compatibility. - * * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger */ trait ProcessableHandlerTrait { /** * @var callable[] + * @phpstan-var array */ protected $processors = []; /** - * {@inheritdoc} - * @suppress PhanTypeMismatchReturn + * {@inheritDoc} */ - public function pushProcessor($callback): HandlerInterface + public function pushProcessor(callable $callback): HandlerInterface { array_unshift($this->processors, $callback); @@ -39,7 +40,7 @@ public function pushProcessor($callback): HandlerInterface } /** - * {@inheritdoc} + * {@inheritDoc} */ public function popProcessor(): callable { @@ -52,6 +53,9 @@ public function popProcessor(): callable /** * Processes a record. + * + * @phpstan-param Record $record + * @phpstan-return Record */ protected function processRecord(array $record): array { diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php index a99e6ab71..36e19cccf 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php @@ -1,4 +1,4 @@ - */ -class PsrHandler extends AbstractHandler +class PsrHandler extends AbstractHandler implements FormattableHandlerInterface { /** * PSR-3 compliant logger @@ -28,12 +33,15 @@ class PsrHandler extends AbstractHandler */ protected $logger; + /** + * @var FormatterInterface|null + */ + protected $formatter; + /** * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied - * @param 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 */ - public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true) + public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, bool $bubble = true) { parent::__construct($level, $bubble); @@ -43,14 +51,45 @@ public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bu /** * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; } - $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + if ($this->formatter) { + $formatted = $this->formatter->format($record); + $this->logger->log(strtolower($record['level_name']), (string) $formatted, $record['context']); + } else { + $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + } return false === $this->bubble; } + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + throw new \LogicException('No formatter has been set and this handler does not have a default formatter'); + } + + return $this->formatter; + } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php index f27bb3da0..fed2303d7 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -1,4 +1,4 @@ - * @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 { + /** @var string */ private $token; + /** @var array */ private $users; + /** @var string */ private $title; - private $user; + /** @var string|int|null */ + private $user = null; + /** @var int */ private $retry; + /** @var int */ private $expire; + /** @var int */ private $highPriorityLevel; + /** @var int */ private $emergencyLevel; + /** @var bool */ private $useFormattedMessage = false; /** * All parameters that can be sent to Pushover * @see https://pushover.net/api - * @var array + * @var array */ - private $parameterNames = array( + private $parameterNames = [ 'token' => true, 'user' => true, 'message' => true, @@ -51,72 +66,103 @@ class PushoverHandler extends SocketHandler 'retry' => true, 'expire' => true, 'callback' => true, - ); + ]; /** * Sounds the api supports by default * @see https://pushover.net/api#sounds - * @var array + * @var string[] */ - private $sounds = array( + private $sounds = [ 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', 'persistent', 'echo', 'updown', 'none', - ); + ]; /** * @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 $title Title sent to the Pushover API - * @param 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|null $title Title sent to the Pushover API * @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 int $highPriorityLevel The minimum logging level at which this handler will start + * @param string|int $highPriorityLevel The minimum logging level at which this handler will start * sending "high priority" requests to the Pushover API - * @param int $emergencyLevel The minimum logging level at which this handler will start + * @param string|int $emergencyLevel The minimum logging level at which this handler will start * sending "emergency" requests to the Pushover API - * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user. - * @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds). + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will + * send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue + * to be retried for (every retry seconds). + * + * @phpstan-param string|array $users + * @phpstan-param Level|LevelName|LogLevel::* $highPriorityLevel + * @phpstan-param Level|LevelName|LogLevel::* $emergencyLevel */ - public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200) - { + public function __construct( + string $token, + $users, + ?string $title = null, + $level = Logger::CRITICAL, + bool $bubble = true, + bool $useSSL = true, + $highPriorityLevel = Logger::CRITICAL, + $emergencyLevel = Logger::EMERGENCY, + int $retry = 30, + int $expire = 25200, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; - parent::__construct($connectionString, $level, $bubble); + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); $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; $this->expire = $expire; } - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); return $this->buildHeader($content) . $content; } - private function buildContent($record) + /** + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string { // Pushover has a limit of 512 characters on title and message combined. $maxMessageLength = 512 - strlen($this->title); $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; - $message = substr($message, 0, $maxMessageLength); + $message = Utils::substr($message, 0, $maxMessageLength); $timestamp = $record['datetime']->getTimestamp(); - $dataArray = array( + $dataArray = [ 'token' => $this->token, 'user' => $this->user, 'message' => $message, 'title' => $this->title, 'timestamp' => $timestamp, - ); + ]; if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { $dataArray['priority'] = 2; @@ -141,7 +187,7 @@ private function buildContent($record) return http_build_query($dataArray); } - private function buildHeader($content) + private function buildHeader(string $content): string { $header = "POST /1/messages.json HTTP/1.1\r\n"; $header .= "Host: api.pushover.net\r\n"; @@ -152,7 +198,7 @@ private function buildHeader($content) return $header; } - protected function write(array $record) + protected function write(array $record): void { foreach ($this->users as $user) { $this->user = $user; @@ -164,22 +210,37 @@ protected function write(array $record) $this->user = null; } - public function setHighPriorityLevel($value) + /** + * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value + */ + public function setHighPriorityLevel($value): self { - $this->highPriorityLevel = $value; + $this->highPriorityLevel = Logger::toMonologLevel($value); + + return $this; } - public function setEmergencyLevel($value) + /** + * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value + */ + public function setEmergencyLevel($value): self { - $this->emergencyLevel = $value; + $this->emergencyLevel = Logger::toMonologLevel($value); + + return $this; } /** * Use the formatted message? - * @param bool $value */ - public function useFormattedMessage($value) + public function useFormattedMessage(bool $value): self { - $this->useFormattedMessage = (bool) $value; + $this->useFormattedMessage = $value; + + return $this; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php deleted file mode 100644 index b0298fa6c..000000000 --- a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php +++ /dev/null @@ -1,234 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\LineFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\Logger; -use Raven_Client; - -/** - * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server - * using sentry-php (https://github.com/getsentry/sentry-php) - * - * @author Marc Abramowitz - */ -class RavenHandler extends AbstractProcessingHandler -{ - /** - * Translates Monolog log levels to Raven log levels. - */ - protected $logLevels = array( - Logger::DEBUG => Raven_Client::DEBUG, - Logger::INFO => Raven_Client::INFO, - Logger::NOTICE => Raven_Client::INFO, - Logger::WARNING => Raven_Client::WARNING, - Logger::ERROR => Raven_Client::ERROR, - Logger::CRITICAL => Raven_Client::FATAL, - Logger::ALERT => Raven_Client::FATAL, - Logger::EMERGENCY => Raven_Client::FATAL, - ); - - /** - * @var string should represent the current version of the calling - * software. Can be any string (git commit, version number) - */ - protected $release; - - /** - * @var Raven_Client the client object that sends the message to the server - */ - protected $ravenClient; - - /** - * @var FormatterInterface The formatter to use for the logs generated via handleBatch() - */ - protected $batchFormatter; - - /** - * @param Raven_Client $ravenClient - * @param 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 - */ - public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) - { - @trigger_error('The Monolog\Handler\RavenHandler class is deprecated. You should rather upgrade to the sentry/sentry 2.x and use Sentry\Monolog\Handler, see https://github.com/getsentry/sentry-php/blob/master/src/Monolog/Handler.php', E_USER_DEPRECATED); - - parent::__construct($level, $bubble); - - $this->ravenClient = $ravenClient; - } - - /** - * {@inheritdoc} - */ - public function handleBatch(array $records) - { - $level = $this->level; - - // filter records based on their level - $records = array_filter($records, function ($record) use ($level) { - return $record['level'] >= $level; - }); - - if (!$records) { - return; - } - - // the record with the highest severity is the "main" one - $record = array_reduce($records, function ($highest, $record) { - if (null === $highest || $record['level'] > $highest['level']) { - return $record; - } - - return $highest; - }); - - // the other ones are added as a context item - $logs = array(); - foreach ($records as $r) { - $logs[] = $this->processRecord($r); - } - - if ($logs) { - $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); - } - - $this->handle($record); - } - - /** - * Sets the formatter for the logs generated by handleBatch(). - * - * @param FormatterInterface $formatter - */ - public function setBatchFormatter(FormatterInterface $formatter) - { - $this->batchFormatter = $formatter; - } - - /** - * Gets the formatter for the logs generated by handleBatch(). - * - * @return FormatterInterface - */ - public function getBatchFormatter() - { - if (!$this->batchFormatter) { - $this->batchFormatter = $this->getDefaultBatchFormatter(); - } - - return $this->batchFormatter; - } - - /** - * {@inheritdoc} - */ - protected function write(array $record) - { - $previousUserContext = false; - $options = array(); - $options['level'] = $this->logLevels[$record['level']]; - $options['tags'] = array(); - if (!empty($record['extra']['tags'])) { - $options['tags'] = array_merge($options['tags'], $record['extra']['tags']); - unset($record['extra']['tags']); - } - if (!empty($record['context']['tags'])) { - $options['tags'] = array_merge($options['tags'], $record['context']['tags']); - unset($record['context']['tags']); - } - if (!empty($record['context']['fingerprint'])) { - $options['fingerprint'] = $record['context']['fingerprint']; - unset($record['context']['fingerprint']); - } - if (!empty($record['context']['logger'])) { - $options['logger'] = $record['context']['logger']; - unset($record['context']['logger']); - } else { - $options['logger'] = $record['channel']; - } - foreach ($this->getExtraParameters() as $key) { - foreach (array('extra', 'context') as $source) { - if (!empty($record[$source][$key])) { - $options[$key] = $record[$source][$key]; - unset($record[$source][$key]); - } - } - } - if (!empty($record['context'])) { - $options['extra']['context'] = $record['context']; - if (!empty($record['context']['user'])) { - $previousUserContext = $this->ravenClient->context->user; - $this->ravenClient->user_context($record['context']['user']); - unset($options['extra']['context']['user']); - } - } - if (!empty($record['extra'])) { - $options['extra']['extra'] = $record['extra']; - } - - if (!empty($this->release) && !isset($options['release'])) { - $options['release'] = $this->release; - } - - if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { - $options['message'] = $record['formatted']; - $this->ravenClient->captureException($record['context']['exception'], $options); - } else { - $this->ravenClient->captureMessage($record['formatted'], array(), $options); - } - - if ($previousUserContext !== false) { - $this->ravenClient->user_context($previousUserContext); - } - } - - /** - * {@inheritDoc} - */ - protected function getDefaultFormatter() - { - return new LineFormatter('[%channel%] %message%'); - } - - /** - * Gets the default formatter for the logs generated by handleBatch(). - * - * @return FormatterInterface - */ - protected function getDefaultBatchFormatter() - { - return new LineFormatter(); - } - - /** - * Gets extra parameters supported by Raven that can be found in "extra" and "context" - * - * @return array - */ - protected function getExtraParameters() - { - return array('contexts', 'checksum', 'release', 'event_id'); - } - - /** - * @param string $value - * @return self - */ - public function setRelease($value) - { - $this->release = $value; - - return $this; - } -} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php index 3725db242..91d16eaf6 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -1,4 +1,4 @@ -pushHandler($redis); * * @author Thomas Tourlourat + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class RedisHandler extends AbstractProcessingHandler { + /** @var \Predis\Client<\Predis\Client>|\Redis */ private $redisClient; + /** @var string */ private $redisKey; + /** @var int */ 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 $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|false $capSize Number of entries to limit list size to + * @param int $capSize Number of entries to limit list size to, 0 = unlimited */ - public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false) + public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true, int $capSize = 0) { if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { throw new \InvalidArgumentException('Predis\Client or Redis instance required'); @@ -54,7 +58,7 @@ public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { if ($this->capSize) { $this->writeCapped($record); @@ -67,10 +71,9 @@ protected function write(array $record) * Write and cap the collection * Writes the record to the redis list and caps its * - * @param array $record associative record array - * @return void + * @phpstan-param FormattedRecord $record */ - protected function writeCapped(array $record) + protected function writeCapped(array $record): void { if ($this->redisClient instanceof \Redis) { $mode = defined('\Redis::MULTI') ? \Redis::MULTI : 1; @@ -91,7 +94,7 @@ protected function writeCapped(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php new file mode 100644 index 000000000..7789309c1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; + +/** + * Sends the message to a Redis Pub/Sub channel using PUBLISH + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisPubSubHandler(new Predis\Client("tcp://localhost:6379"), "logs", Logger::WARNING); + * $log->pushHandler($redis); + * + * @author Gaëtan Faugère + */ +class RedisPubSubHandler extends AbstractProcessingHandler +{ + /** @var \Predis\Client<\Predis\Client>|\Redis */ + private $redisClient; + /** @var string */ + private $channelKey; + + /** + * @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) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->channelKey = $key; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->redisClient->publish($this->channelKey, $record["formatted"]); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php index 65073ffe3..adcc9395a 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -1,4 +1,4 @@ - 'debug', Logger::INFO => 'info', Logger::NOTICE => 'info', @@ -49,7 +48,7 @@ class RollbarHandler extends AbstractProcessingHandler Logger::CRITICAL => 'critical', Logger::ALERT => 'critical', Logger::EMERGENCY => 'critical', - ); + ]; /** * Records whether any log records have been added since the last flush of the rollbar notifier @@ -58,24 +57,23 @@ class RollbarHandler extends AbstractProcessingHandler */ private $hasRecords = false; + /** @var bool */ protected $initialized = false; /** - * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token - * @param 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 RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token */ - public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true) + public function __construct(RollbarLogger $rollbarLogger, $level = Logger::ERROR, bool $bubble = true) { - $this->rollbarNotifier = $rollbarNotifier; + $this->rollbarLogger = $rollbarLogger; parent::__construct($level, $bubble); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { if (!$this->initialized) { // __destructor() doesn't get called on Fatal errors @@ -84,54 +82,45 @@ protected function write(array $record) } $context = $record['context']; - $payload = array(); - if (isset($context['payload'])) { - $payload = $context['payload']; - unset($context['payload']); - } - $context = array_merge($context, $record['extra'], array( + $context = array_merge($context, $record['extra'], [ 'level' => $this->levelMap[$record['level']], 'monolog_level' => $record['level_name'], 'channel' => $record['channel'], 'datetime' => $record['datetime']->format('U'), - )); + ]); - if (isset($context['exception']) && $context['exception'] instanceof Exception) { - $payload['level'] = $context['level']; + if (isset($context['exception']) && $context['exception'] instanceof Throwable) { $exception = $context['exception']; unset($context['exception']); - - $this->rollbarNotifier->report_exception($exception, $context, $payload); + $toLog = $exception; } else { - $this->rollbarNotifier->report_message( - $record['message'], - $context['level'], - $context, - $payload - ); + $toLog = $record['message']; } + // @phpstan-ignore-next-line + $this->rollbarLogger->log($context['level'], $toLog, $context); + $this->hasRecords = true; } - public function flush() + public function flush(): void { if ($this->hasRecords) { - $this->rollbarNotifier->flush(); + $this->rollbarLogger->flush(); $this->hasRecords = false; } } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { $this->flush(); } /** - * {@inheritdoc} + * {@inheritDoc} */ public function reset() { @@ -139,6 +128,4 @@ public function reset() parent::reset(); } - - } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php index b8253ba0f..172685e0a 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -1,4 +1,4 @@ -filename = Utils::canonicalizePath($filename); - $this->maxFiles = (int) $maxFiles; - $this->nextRotation = new \DateTime('tomorrow'); + $this->maxFiles = $maxFiles; + $this->nextRotation = new \DateTimeImmutable('tomorrow'); $this->filenameFormat = '{filename}-{date}'; - $this->dateFormat = 'Y-m-d'; + $this->dateFormat = static::FILE_PER_DAY; parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { parent::close(); @@ -68,7 +73,7 @@ public function close() } /** - * {@inheritdoc} + * {@inheritDoc} */ public function reset() { @@ -79,55 +84,62 @@ public function reset() } } - public function setFilenameFormat($filenameFormat, $dateFormat) + public function setFilenameFormat(string $filenameFormat, string $dateFormat): self { - if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { - trigger_error( + if (!preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + throw new InvalidArgumentException( 'Invalid date format - format must be one of '. 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. - 'date formats using slashes, underscores and/or dots instead of dashes.', - E_USER_DEPRECATED + 'date formats using slashes, underscores and/or dots instead of dashes.' ); } if (substr_count($filenameFormat, '{date}') === 0) { - trigger_error( - 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', - E_USER_DEPRECATED + throw new InvalidArgumentException( + 'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.' ); } $this->filenameFormat = $filenameFormat; $this->dateFormat = $dateFormat; $this->url = $this->getTimedFilename(); $this->close(); + + return $this; } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { - // on the first record written, if the log is new, we should rotate (once per day) + // on the first record written, if the log is new, we rotate (once per day) after the log has been written so that the new file exists if (null === $this->mustRotate) { - $this->mustRotate = !file_exists($this->url); + $this->mustRotate = null === $this->url || !file_exists($this->url); } - if ($this->nextRotation < $record['datetime']) { + // if the next rotation is expired, then we rotate immediately + if ($this->nextRotation <= $record['datetime']) { $this->mustRotate = true; - $this->close(); + $this->close(); // triggers rotation } parent::write($record); + + if ($this->mustRotate) { + $this->close(); // triggers rotation + } } /** * Rotates the files. */ - protected function rotate() + protected function rotate(): void { // update filename $this->url = $this->getTimedFilename(); - $this->nextRotation = new \DateTime('tomorrow'); + $this->nextRotation = new \DateTimeImmutable('tomorrow'); + + $this->mustRotate = false; // skip GC of old logs if files are unlimited if (0 === $this->maxFiles) { @@ -135,6 +147,11 @@ protected function rotate() } $logFiles = glob($this->getGlobPattern()); + if (false === $logFiles) { + // failed to glob + return; + } + if ($this->maxFiles >= count($logFiles)) { // no files to remove return; @@ -149,40 +166,44 @@ protected function rotate() if (is_writable($file)) { // suppress errors here as unlink() might fail if two processes // are cleaning up/rotating at the same time - set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { + return true; + }); unlink($file); restore_error_handler(); } } - - $this->mustRotate = false; } - protected function getTimedFilename() + protected function getTimedFilename(): string { $fileInfo = pathinfo($this->filename); $timedFilename = str_replace( - array('{filename}', '{date}'), - array($fileInfo['filename'], date($this->dateFormat)), + ['{filename}', '{date}'], + [$fileInfo['filename'], date($this->dateFormat)], $fileInfo['dirname'] . '/' . $this->filenameFormat ); - if (!empty($fileInfo['extension'])) { + if (isset($fileInfo['extension'])) { $timedFilename .= '.'.$fileInfo['extension']; } return $timedFilename; } - protected function getGlobPattern() + protected function getGlobPattern(): string { $fileInfo = pathinfo($this->filename); $glob = str_replace( - array('{filename}', '{date}'), - array($fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'), + ['{filename}', '{date}'], + [$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 (!empty($fileInfo['extension'])) { + if (isset($fileInfo['extension'])) { $glob .= '.'.$fileInfo['extension']; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php index b547ed7da..25cce07f9 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -1,4 +1,4 @@ - * @author Kunal Mehta + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ -class SamplingHandler extends AbstractHandler +class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + /** - * @var callable|HandlerInterface $handler + * @var HandlerInterface|callable + * @phpstan-var HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface */ protected $handler; @@ -40,10 +46,12 @@ class SamplingHandler extends AbstractHandler protected $factor; /** + * @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 + * @param int $factor Sample factor (e.g. 10 means every ~10th record is sampled) */ - public function __construct($handler, $factor) + public function __construct($handler, int $factor) { parent::__construct(); $this->handler = $handler; @@ -54,18 +62,17 @@ public function __construct($handler, $factor) } } - public function isHandling(array $record) + public function isHandling(array $record): bool { return $this->getHandler($record)->isHandling($record); } - public function handle(array $record) + public function handle(array $record): bool { if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } $this->getHandler($record)->handle($record); @@ -79,12 +86,14 @@ public function handle(array $record) * * If the handler was provided as a factory callable, this will trigger the handler's instantiation. * + * @phpstan-param Record|array{level: Level}|null $record + * * @return HandlerInterface */ - public function getHandler(array $record = null) + public function getHandler(?array $record = null) { if (!$this->handler instanceof HandlerInterface) { - $this->handler = call_user_func($this->handler, $record, $this); + $this->handler = ($this->handler)($record, $this); if (!$this->handler instanceof HandlerInterface) { throw new \RuntimeException("The factory callable should return a HandlerInterface"); } @@ -94,20 +103,30 @@ public function getHandler(array $record = null) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->getHandler()->setFormatter($formatter); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->getHandler()->getFormatter(); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php new file mode 100644 index 000000000..1280ee703 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * SendGridrHandler uses the SendGrid API v2 function to send Log emails, more information in https://sendgrid.com/docs/API_Reference/Web_API/mail.html + * + * @author Ricardo Fontanelli + */ +class SendGridHandler extends MailHandler +{ + /** + * The SendGrid API User + * @var string + */ + protected $apiUser; + + /** + * The SendGrid API Key + * @var string + */ + protected $apiKey; + + /** + * The email addresses to which the message will be sent + * @var string + */ + protected $from; + + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * @param string $apiUser The SendGrid API User + * @param string $apiKey The SendGrid API Key + * @param string $from The sender of the email + * @param string|string[] $to The recipients of the email + * @param string $subject The subject of the mail + */ + public function __construct(string $apiUser, string $apiKey, string $from, $to, string $subject, $level = Logger::ERROR, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SendGridHandler'); + } + + parent::__construct($level, $bubble); + $this->apiUser = $apiUser; + $this->apiKey = $apiKey; + $this->from = $from; + $this->to = (array) $to; + $this->subject = $subject; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $message = []; + $message['api_user'] = $this->apiUser; + $message['api_key'] = $this->apiKey; + $message['from'] = $this->from; + foreach ($this->to as $recipient) { + $message['to[]'] = $recipient; + } + $message['subject'] = $this->subject; + $message['date'] = date('r'); + + if ($this->isHtmlBody($content)) { + $message['html'] = $content; + } else { + $message['text'] = $content; + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://api.sendgrid.com/api/mail.send.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($message)); + Curl\Util::execute($ch, 2); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php index 39455501f..9ae100371 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -1,4 +1,4 @@ - * @see https://api.slack.com/incoming-webhooks * @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 { - const COLOR_DANGER = 'danger'; + public const COLOR_DANGER = 'danger'; - const COLOR_WARNING = 'warning'; + public const COLOR_WARNING = 'warning'; - const COLOR_GOOD = 'good'; + public const COLOR_GOOD = 'good'; - const COLOR_DEFAULT = '#e3e4e6'; + public const COLOR_DEFAULT = '#e3e4e6'; /** * Slack channel (encoded ID or name) @@ -48,7 +51,7 @@ class SlackRecord /** * User icon e.g. 'ghost', 'http://example.com/user.png' - * @var string + * @var string|null */ private $userIcon; @@ -72,12 +75,12 @@ class SlackRecord /** * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] - * @var array + * @var string[] */ private $excludeFields; /** - * @var FormatterInterface + * @var ?FormatterInterface */ private $formatter; @@ -86,26 +89,45 @@ class SlackRecord */ private $normalizerFormatter; - public function __construct($channel = null, $username = null, $useAttachment = true, $userIcon = null, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array(), FormatterInterface $formatter = null) - { - $this->channel = $channel; - $this->username = $username; - $this->userIcon = trim($userIcon, ':'); - $this->useAttachment = $useAttachment; - $this->useShortAttachment = $useShortAttachment; - $this->includeContextAndExtra = $includeContextAndExtra; - $this->excludeFields = $excludeFields; - $this->formatter = $formatter; + /** + * @param string[] $excludeFields + */ + public function __construct( + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $userIcon = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = array(), + ?FormatterInterface $formatter = null + ) { + $this + ->setChannel($channel) + ->setUsername($username) + ->useAttachment($useAttachment) + ->setUserIcon($userIcon) + ->useShortAttachment($useShortAttachment) + ->includeContextAndExtra($includeContextAndExtra) + ->excludeFields($excludeFields) + ->setFormatter($formatter); if ($this->includeContextAndExtra) { $this->normalizerFormatter = new NormalizerFormatter(); } } - public function getSlackData(array $record) + /** + * Returns required data in format that Slack + * is expecting. + * + * @phpstan-param FormattedRecord $record + * @phpstan-return mixed[] + */ + public function getSlackData(array $record): array { $dataArray = array(); - $record = $this->excludeFields($record); + $record = $this->removeExcludedFields($record); if ($this->username) { $dataArray['username'] = $this->username; @@ -116,6 +138,7 @@ public function getSlackData(array $record) } if ($this->formatter && !$this->useAttachment) { + /** @phpstan-ignore-next-line */ $message = $this->formatter->format($record); } else { $message = $record['message']; @@ -123,12 +146,14 @@ public function getSlackData(array $record) if ($this->useAttachment) { $attachment = array( - 'fallback' => $message, - 'text' => $message, - 'color' => $this->getAttachmentColor($record['level']), - 'fields' => array(), - 'mrkdwn_in' => array('fields'), - 'ts' => $record['datetime']->getTimestamp() + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp(), + 'footer' => $this->username, + 'footer_icon' => $this->userIcon, ); if ($this->useShortAttachment) { @@ -138,7 +163,6 @@ public function getSlackData(array $record) $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); } - if ($this->includeContextAndExtra) { foreach (array('extra', 'context') as $key) { if (empty($record[$key])) { @@ -147,7 +171,7 @@ public function getSlackData(array $record) if ($this->useShortAttachment) { $attachment['fields'][] = $this->generateAttachmentField( - $key, + (string) $key, $record[$key] ); } else { @@ -177,93 +201,157 @@ public function getSlackData(array $record) } /** - * Returned a Slack message attachment color associated with + * Returns a Slack message attachment color associated with * provided level. - * - * @param int $level - * @return string */ - public function getAttachmentColor($level) + public function getAttachmentColor(int $level): string { switch (true) { case $level >= Logger::ERROR: - return self::COLOR_DANGER; + return static::COLOR_DANGER; case $level >= Logger::WARNING: - return self::COLOR_WARNING; + return static::COLOR_WARNING; case $level >= Logger::INFO: - return self::COLOR_GOOD; + return static::COLOR_GOOD; default: - return self::COLOR_DEFAULT; + return static::COLOR_DEFAULT; } } /** * Stringifies an array of key/value pairs to be used in attachment fields * - * @param array $fields - * - * @return string + * @param mixed[] $fields */ - public function stringify($fields) + public function stringify(array $fields): string { + /** @var Record $fields */ $normalized = $this->normalizerFormatter->format($fields); - $prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; - $flags = 0; - if (PHP_VERSION_ID >= 50400) { - $flags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; - } $hasSecondDimension = count(array_filter($normalized, 'is_array')); $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric')); return $hasSecondDimension || $hasNonNumericKeys - ? Utils::jsonEncode($normalized, $prettyPrintFlag | $flags) - : Utils::jsonEncode($normalized, $flags); + ? Utils::jsonEncode($normalized, JSON_PRETTY_PRINT|Utils::DEFAULT_JSON_FLAGS) + : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS); } /** - * Sets the formatter + * Channel used by the bot when posting + * + * @param ?string $channel * - * @param FormatterInterface $formatter + * @return static + */ + public function setChannel(?string $channel = null): self + { + $this->channel = $channel; + + return $this; + } + + /** + * Username used by the bot when posting + * + * @param ?string $username + * + * @return static + */ + public function setUsername(?string $username = null): self + { + $this->username = $username; + + return $this; + } + + public function useAttachment(bool $useAttachment = true): self + { + $this->useAttachment = $useAttachment; + + return $this; + } + + public function setUserIcon(?string $userIcon = null): self + { + $this->userIcon = $userIcon; + + if (\is_string($userIcon)) { + $this->userIcon = trim($userIcon, ':'); + } + + return $this; + } + + public function useShortAttachment(bool $useShortAttachment = false): self + { + $this->useShortAttachment = $useShortAttachment; + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra = false): self + { + $this->includeContextAndExtra = $includeContextAndExtra; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + + return $this; + } + + /** + * @param string[] $excludeFields */ - public function setFormatter(FormatterInterface $formatter) + public function excludeFields(array $excludeFields = []): self + { + $this->excludeFields = $excludeFields; + + return $this; + } + + public function setFormatter(?FormatterInterface $formatter = null): self { $this->formatter = $formatter; + + return $this; } /** * Generates attachment field * - * @param string $title - * @param string|array $value + * @param string|mixed[] $value * - * @return array + * @return array{title: string, value: string, short: false} */ - private function generateAttachmentField($title, $value) + private function generateAttachmentField(string $title, $value): array { $value = is_array($value) - ? sprintf('```%s```', $this->stringify($value)) + ? sprintf('```%s```', substr($this->stringify($value), 0, 1990)) : $value; return array( 'title' => ucfirst($title), 'value' => $value, - 'short' => false + 'short' => false, ); } /** * Generates a collection of attachment fields from array * - * @param array $data + * @param mixed[] $data * - * @return array + * @return array */ - private function generateAttachmentFields(array $data) + private function generateAttachmentFields(array $data): array { + /** @var Record $data */ + $normalized = $this->normalizerFormatter->format($data); + $fields = array(); - foreach ($this->normalizerFormatter->format($data) as $key => $value) { - $fields[] = $this->generateAttachmentField($key, $value); + foreach ($normalized as $key => $value) { + $fields[] = $this->generateAttachmentField((string) $key, $value); } return $fields; @@ -272,11 +360,11 @@ private function generateAttachmentFields(array $data) /** * Get a copy of record with fields excluded according to $this->excludeFields * - * @param array $record + * @phpstan-param FormattedRecord $record * - * @return array + * @return mixed[] */ - private function excludeFields(array $record) + private function removeExcludedFields(array $record): array { foreach ($this->excludeFields as $field) { $keys = explode('.', $field); diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php index 88c4c4d00..a648513e0 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -1,4 +1,4 @@ - * @see https://api.slack.com/ + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class SlackHandler extends SocketHandler { @@ -42,20 +44,42 @@ class SlackHandler extends SocketHandler * @param string|null $username Name of a bot * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) * @param string|null $iconEmoji The emoji name to use (or null) - * @param 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 $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $useShortAttachment Whether the context/extra messages added to Slack as attachments are in a short style * @param bool $includeContextAndExtra Whether the attachment should include context and extra data - * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] * @throws MissingExtensionException If no OpenSSL PHP extension configured */ - public function __construct($token, $channel, $username = null, $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array()) - { + public function __construct( + string $token, + string $channel, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + $level = Logger::CRITICAL, + bool $bubble = true, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = array(), + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { if (!extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); } - parent::__construct('ssl://slack.com:443', $level, $bubble); + parent::__construct( + 'ssl://slack.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); $this->slackRecord = new SlackRecord( $channel, @@ -64,30 +88,26 @@ public function __construct($token, $channel, $username = null, $useAttachment = $iconEmoji, $useShortAttachment, $includeContextAndExtra, - $excludeFields, - $this->formatter + $excludeFields ); $this->token = $token; } - public function getSlackRecord() + public function getSlackRecord(): SlackRecord { return $this->slackRecord; } - public function getToken() + public function getToken(): string { return $this->token; } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); @@ -97,10 +117,9 @@ protected function generateDataStream($record) /** * Builds the body of API call * - * @param array $record - * @return string + * @phpstan-param FormattedRecord $record */ - private function buildContent($record) + private function buildContent(array $record): string { $dataArray = $this->prepareContentData($record); @@ -108,12 +127,10 @@ private function buildContent($record) } /** - * Prepares content data - * - * @param array $record - * @return array + * @phpstan-param FormattedRecord $record + * @return string[] */ - protected function prepareContentData($record) + protected function prepareContentData(array $record): array { $dataArray = $this->slackRecord->getSlackData($record); $dataArray['token'] = $this->token; @@ -127,11 +144,8 @@ protected function prepareContentData($record) /** * Builds the header of the API Call - * - * @param string $content - * @return string */ - private function buildHeader($content) + private function buildHeader(string $content): string { $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; $header .= "Host: slack.com\r\n"; @@ -143,11 +157,9 @@ private function buildHeader($content) } /** - * {@inheritdoc} - * - * @param array $record + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { parent::write($record); $this->finalizeWrite(); @@ -159,7 +171,7 @@ protected function write(array $record) * If we do not read some but close the socket too early, slack sometimes * drops the request entirely. */ - protected function finalizeWrite() + protected function finalizeWrite(): void { $res = $this->getResource(); if (is_resource($res)) { @@ -168,54 +180,77 @@ protected function finalizeWrite() $this->closeSocket(); } + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } + /** - * Returned a Slack message attachment color associated with - * provided level. - * - * @param int $level - * @return string - * @deprecated Use underlying SlackRecord instead + * Channel used by the bot when posting */ - protected function getAttachmentColor($level) + public function setChannel(string $channel): self { - trigger_error( - 'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.', - E_USER_DEPRECATED - ); + $this->slackRecord->setChannel($channel); - return $this->slackRecord->getAttachmentColor($level); + return $this; } /** - * Stringifies an array of key/value pairs to be used in attachment fields - * - * @param array $fields - * @return string - * @deprecated Use underlying SlackRecord instead + * Username used by the bot when posting */ - protected function stringify($fields) + public function setUsername(string $username): self { - trigger_error( - 'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.', - E_USER_DEPRECATED - ); + $this->slackRecord->setUsername($username); - return $this->slackRecord->stringify($fields); + return $this; } - public function setFormatter(FormatterInterface $formatter) + public function useAttachment(bool $useAttachment): self { - parent::setFormatter($formatter); - $this->slackRecord->setFormatter($formatter); + $this->slackRecord->useAttachment($useAttachment); return $this; } - public function getFormatter() + public function setIconEmoji(string $iconEmoji): self { - $formatter = parent::getFormatter(); - $this->slackRecord->setFormatter($formatter); + $this->slackRecord->setUserIcon($iconEmoji); - return $formatter; + return $this; + } + + public function useShortAttachment(bool $useShortAttachment): self + { + $this->slackRecord->useShortAttachment($useShortAttachment); + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra): self + { + $this->slackRecord->includeContextAndExtra($includeContextAndExtra); + + return $this; + } + + /** + * @param string[] $excludeFields + */ + public function excludeFields(array $excludeFields): self + { + $this->slackRecord->excludeFields($excludeFields); + + return $this; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php index b87be99a8..8ae3c7882 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php @@ -1,4 +1,4 @@ -webhookUrl = $webhookUrl; @@ -61,27 +73,24 @@ public function __construct($webhookUrl, $channel = null, $username = null, $use $iconEmoji, $useShortAttachment, $includeContextAndExtra, - $excludeFields, - $this->formatter + $excludeFields ); } - public function getSlackRecord() + public function getSlackRecord(): SlackRecord { return $this->slackRecord; } - public function getWebhookUrl() + public function getWebhookUrl(): string { return $this->webhookUrl; } /** - * {@inheritdoc} - * - * @param array $record + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $postData = $this->slackRecord->getSlackData($record); $postString = Utils::jsonEncode($postData); @@ -92,7 +101,7 @@ protected function write(array $record) CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => array('Content-type: application/json'), - CURLOPT_POSTFIELDS => $postString + CURLOPT_POSTFIELDS => $postString, ); if (defined('CURLOPT_SAFE_UPLOAD')) { $options[CURLOPT_SAFE_UPLOAD] = true; @@ -103,7 +112,7 @@ protected function write(array $record) Curl\Util::execute($ch); } - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { parent::setFormatter($formatter); $this->slackRecord->setFormatter($formatter); @@ -111,7 +120,7 @@ public function setFormatter(FormatterInterface $formatter) return $this; } - public function getFormatter() + public function getFormatter(): FormatterInterface { $formatter = parent::getFormatter(); $this->slackRecord->setFormatter($formatter); diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php deleted file mode 100644 index d3352ea0f..000000000 --- a/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Logger; - -/** - * Sends notifications through Slack's Slackbot - * - * @author Haralan Dobrev - * @see https://slack.com/apps/A0F81R8ET-slackbot - * @deprecated According to Slack the API used on this handler it is deprecated. - * Therefore this handler will be removed on 2.x - * Slack suggests to use webhooks instead. Please contact slack for more information. - */ -class SlackbotHandler extends AbstractProcessingHandler -{ - /** - * The slug of the Slack team - * @var string - */ - private $slackTeam; - - /** - * Slackbot token - * @var string - */ - private $token; - - /** - * Slack channel name - * @var string - */ - private $channel; - - /** - * @param string $slackTeam Slack team slug - * @param string $token Slackbot token - * @param string $channel Slack channel (encoded ID or name) - * @param 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 - */ - public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true) - { - @trigger_error('SlackbotHandler is deprecated and will be removed on 2.x', E_USER_DEPRECATED); - parent::__construct($level, $bubble); - - $this->slackTeam = $slackTeam; - $this->token = $token; - $this->channel = $channel; - } - - /** - * {@inheritdoc} - * - * @param array $record - */ - protected function write(array $record) - { - $slackbotUrl = sprintf( - 'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s', - $this->slackTeam, - $this->token, - $this->channel - ); - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $slackbotUrl); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']); - - Curl\Util::execute($ch); - } -} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php index db50d97fe..21701afa2 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -1,4 +1,4 @@ - * @see http://php.net/manual/en/function.fsockopen.php + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class SocketHandler extends AbstractProcessingHandler { + /** @var string */ private $connectionString; + /** @var float */ private $connectionTimeout; + /** @var resource|null */ private $resource; - private $timeout = 0; - private $writingTimeout = 10; + /** @var float */ + private $timeout; + /** @var float */ + private $writingTimeout; + /** @var ?int */ private $lastSentBytes = null; - private $chunkSize = null; - private $persistent = false; - private $errno; - private $errstr; - private $lastWritingAt; + /** @var ?int */ + private $chunkSize; + /** @var bool */ + private $persistent; + /** @var ?int */ + private $errno = null; + /** @var ?string */ + private $errstr = null; + /** @var ?float */ + private $lastWritingAt = null; /** - * @param string $connectionString Socket connection string - * @param 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 $connectionString Socket connection string + * @param bool $persistent Flag to enable/disable persistent connections + * @param float $timeout Socket timeout to wait until the request is being aborted + * @param float $writingTimeout Socket timeout to wait until the request should've been sent/written + * @param float|null $connectionTimeout Socket connect timeout to wait until the connection should've been + * established + * @param int|null $chunkSize Sets the chunk size. Only has effect during connection in the writing cycle + * + * @throws \InvalidArgumentException If an invalid timeout value (less than 0) is passed. */ - public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) - { + public function __construct( + string $connectionString, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { parent::__construct($level, $bubble); $this->connectionString = $connectionString; - $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + + if ($connectionTimeout !== null) { + $this->validateTimeout($connectionTimeout); + } + + $this->connectionTimeout = $connectionTimeout ?? (float) ini_get('default_socket_timeout'); + $this->persistent = $persistent; + $this->validateTimeout($timeout); + $this->timeout = $timeout; + $this->validateTimeout($writingTimeout); + $this->writingTimeout = $writingTimeout; + $this->chunkSize = $chunkSize; } /** * Connect (if necessary) and write to the socket * - * @param array $record + * {@inheritDoc} * * @throws \UnexpectedValueException * @throws \RuntimeException */ - protected function write(array $record) + protected function write(array $record): void { $this->connectIfNotConnected(); $data = $this->generateDataStream($record); @@ -63,7 +102,7 @@ protected function write(array $record) /** * We will not close a PersistentSocket instance so it can be reused in other requests. */ - public function close() + public function close(): void { if (!$this->isPersistent()) { $this->closeSocket(); @@ -73,7 +112,7 @@ public function close() /** * Close socket, if open */ - public function closeSocket() + public function closeSocket(): void { if (is_resource($this->resource)) { fclose($this->resource); @@ -82,39 +121,39 @@ public function closeSocket() } /** - * Set socket connection to nbe persistent. It only has effect before the connection is initiated. - * - * @param bool $persistent + * Set socket connection to be persistent. It only has effect before the connection is initiated. */ - public function setPersistent($persistent) + public function setPersistent(bool $persistent): self { - $this->persistent = (bool) $persistent; + $this->persistent = $persistent; + + return $this; } /** * Set connection timeout. Only has effect before we connect. * - * @param float $seconds - * * @see http://php.net/manual/en/function.fsockopen.php */ - public function setConnectionTimeout($seconds) + public function setConnectionTimeout(float $seconds): self { $this->validateTimeout($seconds); - $this->connectionTimeout = (float) $seconds; + $this->connectionTimeout = $seconds; + + return $this; } /** * Set write timeout. Only has effect before we connect. * - * @param float $seconds - * * @see http://php.net/manual/en/function.stream-set-timeout.php */ - public function setTimeout($seconds) + public function setTimeout(float $seconds): self { $this->validateTimeout($seconds); - $this->timeout = (float) $seconds; + $this->timeout = $seconds; + + return $this; } /** @@ -122,58 +161,52 @@ public function setTimeout($seconds) * * @param float $seconds 0 for no timeout */ - public function setWritingTimeout($seconds) + public function setWritingTimeout(float $seconds): self { $this->validateTimeout($seconds); - $this->writingTimeout = (float) $seconds; + $this->writingTimeout = $seconds; + + return $this; } /** * Set chunk size. Only has effect during connection in the writing cycle. - * - * @param float $bytes */ - public function setChunkSize($bytes) + public function setChunkSize(int $bytes): self { $this->chunkSize = $bytes; + + return $this; } /** * Get current connection string - * - * @return string */ - public function getConnectionString() + public function getConnectionString(): string { return $this->connectionString; } /** * Get persistent setting - * - * @return bool */ - public function isPersistent() + public function isPersistent(): bool { return $this->persistent; } /** * Get current connection timeout setting - * - * @return float */ - public function getConnectionTimeout() + public function getConnectionTimeout(): float { return $this->connectionTimeout; } /** * Get current in-transfer timeout - * - * @return float */ - public function getTimeout() + public function getTimeout(): float { return $this->timeout; } @@ -183,17 +216,15 @@ public function getTimeout() * * @return float */ - public function getWritingTimeout() + public function getWritingTimeout(): float { return $this->writingTimeout; } /** * Get current chunk size - * - * @return float */ - public function getChunkSize() + public function getChunkSize(): ?int { return $this->chunkSize; } @@ -202,10 +233,8 @@ public function getChunkSize() * Check to see if the socket is currently available. * * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. - * - * @return bool */ - public function isConnected() + public function isConnected(): bool { return is_resource($this->resource) && !feof($this->resource); // on TCP - other party can close connection. @@ -213,6 +242,8 @@ public function isConnected() /** * Wrapper to allow mocking + * + * @return resource|false */ protected function pfsockopen() { @@ -221,6 +252,8 @@ protected function pfsockopen() /** * Wrapper to allow mocking + * + * @return resource|false */ protected function fsockopen() { @@ -231,50 +264,77 @@ protected function fsockopen() * Wrapper to allow mocking * * @see http://php.net/manual/en/function.stream-set-timeout.php + * + * @return bool */ protected function streamSetTimeout() { $seconds = floor($this->timeout); $microseconds = round(($this->timeout - $seconds) * 1e6); - return stream_set_timeout($this->resource, $seconds, $microseconds); + 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); } /** * Wrapper to allow mocking * * @see http://php.net/manual/en/function.stream-set-chunk-size.php + * + * @return int|bool */ protected function streamSetChunkSize() { + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetChunkSize called but $this->resource is not a resource'); + } + + if (null === $this->chunkSize) { + throw new \LogicException('streamSetChunkSize called but $this->chunkSize is not set'); + } + return stream_set_chunk_size($this->resource, $this->chunkSize); } /** * Wrapper to allow mocking + * + * @return int|bool */ - protected function fwrite($data) + 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); } /** * Wrapper to allow mocking + * + * @return mixed[]|bool */ 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); } - private function validateTimeout($value) + private function validateTimeout(float $value): void { - $ok = filter_var($value, FILTER_VALIDATE_FLOAT); - if ($ok === false || $value < 0) { + if ($value < 0) { throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); } } - private function connectIfNotConnected() + private function connectIfNotConnected(): void { if ($this->isConnected()) { return; @@ -282,7 +342,10 @@ private function connectIfNotConnected() $this->connect(); } - protected function generateDataStream($record) + /** + * @phpstan-param FormattedRecord $record + */ + protected function generateDataStream(array $record): string { return (string) $record['formatted']; } @@ -295,41 +358,41 @@ protected function getResource() return $this->resource; } - private function connect() + private function connect(): void { $this->createSocketResource(); $this->setSocketTimeout(); $this->setStreamChunkSize(); } - private function createSocketResource() + private function createSocketResource(): void { if ($this->isPersistent()) { $resource = $this->pfsockopen(); } 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; } - private function setSocketTimeout() + private function setSocketTimeout(): void { if (!$this->streamSetTimeout()) { throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); } } - private function setStreamChunkSize() + private function setStreamChunkSize(): void { if ($this->chunkSize && !$this->streamSetChunkSize()) { throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); } } - private function writeToSocket($data) + private function writeToSocket(string $data): void { $length = strlen($data); $sent = 0; @@ -345,7 +408,7 @@ private function writeToSocket($data) } $sent += $chunk; $socketInfo = $this->streamGetMetadata(); - if ($socketInfo['timed_out']) { + if (is_array($socketInfo) && $socketInfo['timed_out']) { throw new \RuntimeException("Write timed-out"); } @@ -358,15 +421,15 @@ private function writeToSocket($data) } } - private function writingIsTimedOut($sent) + private function writingIsTimedOut(int $sent): bool { - $writingTimeout = (int) floor($this->writingTimeout); - if (0 === $writingTimeout) { + // convert to ms + if (0.0 == $this->writingTimeout) { return false; } if ($sent !== $this->lastSentBytes) { - $this->lastWritingAt = time(); + $this->lastWritingAt = microtime(true); $this->lastSentBytes = $sent; return false; @@ -374,7 +437,7 @@ private function writingIsTimedOut($sent) usleep(100); } - if ((time() - $this->lastWritingAt) >= $writingTimeout) { + if ((microtime(true) - $this->lastWritingAt) >= $this->writingTimeout) { $this->closeSocket(); return true; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php new file mode 100644 index 000000000..dcf282b45 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sqs\SqsClient; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Writes to any sqs queue. + * + * @author Martijn van Calker + */ +class SqsHandler extends AbstractProcessingHandler +{ + /** 256 KB in bytes - maximum message size in SQS */ + protected const MAX_MESSAGE_SIZE = 262144; + /** 100 KB in bytes - head message size for new error log */ + protected const HEAD_MESSAGE_SIZE = 102400; + + /** @var SqsClient */ + private $client; + /** @var string */ + private $queueUrl; + + public function __construct(SqsClient $sqsClient, string $queueUrl, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->client = $sqsClient; + $this->queueUrl = $queueUrl; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!isset($record['formatted']) || 'string' !== gettype($record['formatted'])) { + throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string' . Utils::getRecordMessageForException($record)); + } + + $messageBody = $record['formatted']; + if (strlen($messageBody) >= static::MAX_MESSAGE_SIZE) { + $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE); + } + + $this->client->sendMessage([ + 'QueueUrl' => $this->queueUrl, + 'MessageBody' => $messageBody, + ]); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php index 74a613cb1..218d43847 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class StreamHandler extends AbstractProcessingHandler { - /** @private 512KB */ - const CHUNK_SIZE = 524288; - + /** @const int */ + protected const MAX_CHUNK_SIZE = 2147483647; + /** @const int 10MB */ + protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024; + /** @var int */ + protected $streamChunkSize; /** @var resource|null */ protected $stream; - protected $url; - private $errorMessage; + /** @var ?string */ + protected $url = null; + /** @var ?string */ + private $errorMessage = null; + /** @var ?int */ protected $filePermission; + /** @var bool */ protected $useLocking; - private $dirCreated; + /** @var string */ + protected $fileOpenMode; + /** @var true|null */ + private $dirCreated = null; + /** @var bool */ + private $retrying = false; /** - * @param resource|string $stream - * @param 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 resource|string $stream If a missing path can't be created, an UnexpectedValueException will be thrown on first write * @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 + * @param string $fileOpenMode The fopen() mode used when opening a file, if $stream is a file path * - * @throws \Exception If a missing directory is not buildable * @throws \InvalidArgumentException If stream is not a resource or string */ - public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + public function __construct($stream, $level = Logger::DEBUG, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false, $fileOpenMode = 'a') { parent::__construct($level, $bubble); + + if (($phpMemoryLimit = Utils::expandIniShorthandBytes(ini_get('memory_limit'))) !== false) { + if ($phpMemoryLimit > 0) { + // use max 10% of allowed memory for the chunk size, and at least 100KB + $this->streamChunkSize = min(static::MAX_CHUNK_SIZE, max((int) ($phpMemoryLimit / 10), 100 * 1024)); + } else { + // memory is unlimited, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + } else { + // no memory limit information, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + if (is_resource($stream)) { $this->stream = $stream; - $this->streamSetChunkSize(); + + stream_set_chunk_size($this->stream, $this->streamChunkSize); } elseif (is_string($stream)) { $this->url = Utils::canonicalizePath($stream); } else { throw new \InvalidArgumentException('A stream must either be a resource or a string.'); } + $this->fileOpenMode = $fileOpenMode; $this->filePermission = $filePermission; $this->useLocking = $useLocking; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { if ($this->url && is_resource($this->stream)) { fclose($this->stream); @@ -87,78 +115,110 @@ public function getStream() * * @return string|null */ - public function getUrl() + public function getUrl(): ?string { return $this->url; } /** - * {@inheritdoc} + * @return int */ - protected function write(array $record) + public function getStreamChunkSize(): int + { + return $this->streamChunkSize; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void { if (!is_resource($this->stream)) { - if (null === $this->url || '' === $this->url) { - throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + $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().' . Utils::getRecordMessageForException($record)); } - $this->createDir(); + $this->createDir($url); $this->errorMessage = null; - set_error_handler(array($this, 'customErrorHandler')); - $this->stream = fopen($this->url, 'a'); - if ($this->filePermission !== null) { - @chmod($this->url, $this->filePermission); + set_error_handler(function (...$args) { + return $this->customErrorHandler(...$args); + }); + try { + $stream = fopen($url, $this->fileOpenMode); + if ($this->filePermission !== null) { + @chmod($url, $this->filePermission); + } + } finally { + restore_error_handler(); } - 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) . Utils::getRecordMessageForException($record)); } - $this->streamSetChunkSize(); + stream_set_chunk_size($stream, $this->streamChunkSize); + $this->stream = $stream; + } + + $stream = $this->stream; + if (!is_resource($stream)) { + throw new \LogicException('No stream was opened yet' . Utils::getRecordMessageForException($record)); } 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->errorMessage = null; + set_error_handler(function (...$args) { + return $this->customErrorHandler(...$args); + }); + try { + $this->streamWrite($stream, $record); + } finally { + restore_error_handler(); } + if ($this->errorMessage !== null) { + $error = $this->errorMessage; + // close the resource if possible to reopen it, and retry the failed write + if (!$this->retrying && $this->url !== null && $this->url !== 'php://memory') { + $this->retrying = true; + $this->close(); + $this->write($record); - $this->streamWrite($this->stream, $record); + return; + } + + throw new \UnexpectedValueException('Writing to the log file failed: '.$error . Utils::getRecordMessageForException($record)); + } + $this->retrying = false; if ($this->useLocking) { - flock($this->stream, LOCK_UN); + flock($stream, LOCK_UN); } } /** * Write to stream * @param resource $stream - * @param array $record + * @param array $record + * + * @phpstan-param FormattedRecord $record */ - protected function streamWrite($stream, array $record) + protected function streamWrite($stream, array $record): void { fwrite($stream, (string) $record['formatted']); } - protected function streamSetChunkSize() + private function customErrorHandler(int $code, string $msg): bool { - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - return stream_set_chunk_size($this->stream, self::CHUNK_SIZE); - } - - return false; - } + $this->errorMessage = preg_replace('{^(fopen|mkdir|fwrite)\(.*?\): }', '', $msg); - private function customErrorHandler($code, $msg) - { - $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + return true; } - /** - * @param string $stream - * - * @return null|string - */ - private function getDirFromStream($stream) + private function getDirFromStream(string $stream): ?string { $pos = strpos($stream, '://'); if ($pos === false) { @@ -172,21 +232,23 @@ private function getDirFromStream($stream) return null; } - private function createDir() + 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(array($this, 'customErrorHandler')); + set_error_handler(function (...$args) { + return $this->customErrorHandler(...$args); + }); $status = mkdir($dir, 0777, true); restore_error_handler(); - if (false === $status && !is_dir($dir)) { - throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $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)); } } $this->dirCreated = true; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php index ac7b16ff8..fae925141 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php @@ -1,4 +1,4 @@ -mailer = $mailer; $this->messageTemplate = $message; } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records): void { $this->mailer->send($this->buildMessage($content, $records)); } @@ -51,10 +60,9 @@ protected function send($content, array $records) /** * Gets the formatter for the Swift_Message subject. * - * @param string $format The format of the subject - * @return FormatterInterface + * @param string|null $format The format of the subject */ - protected function getSubjectFormatter($format) + protected function getSubjectFormatter(?string $format): FormatterInterface { return new LineFormatter($format); } @@ -62,22 +70,25 @@ protected function getSubjectFormatter($format) /** * Creates instance of Swift_Message to be sent * - * @param string $content formatted email body to be sent - * @param array $records Log records that formed the content - * @return \Swift_Message + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return Swift_Message + * + * @phpstan-param Record[] $records */ - protected function buildMessage($content, array $records) + protected function buildMessage(string $content, array $records): Swift_Message { $message = null; - if ($this->messageTemplate instanceof \Swift_Message) { + if ($this->messageTemplate instanceof Swift_Message) { $message = clone $this->messageTemplate; $message->generateId(); } elseif (is_callable($this->messageTemplate)) { - $message = call_user_func($this->messageTemplate, $content, $records); + $message = ($this->messageTemplate)($content, $records); } - if (!$message instanceof \Swift_Message) { - throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it'); + if (!$message instanceof Swift_Message) { + $record = reset($records); + throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : '')); } if ($records) { @@ -85,27 +96,20 @@ protected function buildMessage($content, array $records) $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); } - $message->setBody($content); + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ if (version_compare(Swift::VERSION, '6.0.0', '>=')) { $message->setDate(new \DateTimeImmutable()); } else { + /** @phpstan-ignore-next-line */ $message->setDate(time()); } return $message; } - - /** - * BC getter, to be removed in 2.0 - */ - public function __get($name) - { - if ($name === 'message') { - trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED); - - return $this->buildMessage(null, array()); - } - - throw new \InvalidArgumentException('Invalid property '.$name); - } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php new file mode 100644 index 000000000..130e6f1f3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mime\Email; + +/** + * SymfonyMailerHandler uses Symfony's Mailer component to send the emails + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class SymfonyMailerHandler extends MailHandler +{ + /** @var MailerInterface|TransportInterface */ + protected $mailer; + /** @var Email|callable(string, Record[]): Email */ + private $emailTemplate; + + /** + * @psalm-param Email|callable(string, Record[]): Email $email + * + * @param MailerInterface|TransportInterface $mailer The mailer to use + * @param callable|Email $email An email template, the subject/body will be replaced + */ + public function __construct($mailer, $email, $level = Logger::ERROR, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->emailTemplate = $email; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string|null $format The format of the subject + */ + protected function getSubjectFormatter(?string $format): FormatterInterface + { + return new LineFormatter($format); + } + + /** + * Creates instance of Email to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * + * @phpstan-param Record[] $records + */ + protected function buildMessage(string $content, array $records): Email + { + $message = null; + if ($this->emailTemplate instanceof Email) { + $message = clone $this->emailTemplate; + } elseif (is_callable($this->emailTemplate)) { + $message = ($this->emailTemplate)($content, $records); + } + + if (!$message instanceof Email) { + $record = reset($records); + throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : '')); + } + + if ($records) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->subject($subjectFormatter->format($this->getHighestRecord($records))); + } + + if ($this->isHtmlBody($content)) { + if (null !== ($charset = $message->getHtmlCharset())) { + $message->html($content, $charset); + } else { + $message->html($content); + } + } else { + if (null !== ($charset = $message->getTextCharset())) { + $message->text($content, $charset); + } else { + $message->text($content); + } + } + + return $message->date(new \DateTimeImmutable()); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php index f770c8028..1d543b7ec 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -1,4 +1,4 @@ -facilities, or a LOG_* facility constant + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID */ - public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID) + public function __construct(string $ident, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, int $logopts = LOG_PID) { parent::__construct($facility, $level, $bubble); @@ -47,20 +48,20 @@ public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { closelog(); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { if (!openlog($this->ident, $this->logopts, $this->facility)) { - throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"' . Utils::getRecordMessageForException($record)); } syslog($this->logLevels[$record['level']], (string) $record['formatted']); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php index 3bff085bf..dbd8ef69d 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -1,4 +1,4 @@ -ip = $ip; $this->port = $port; - $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); } + /** + * @param string $line + * @param string $header + * @return void + */ public function write($line, $header = "") { $this->send($this->assembleMessage($line, $header)); } - public function close() + public function close(): void { - if (is_resource($this->socket)) { + if (is_resource($this->socket) || $this->socket instanceof Socket) { socket_close($this->socket); $this->socket = null; } } - protected function send($chunk) + /** + * @return resource|Socket + */ + protected function getSocket() { - if (!is_resource($this->socket)) { - throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); + 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'); } - socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + + return $this->socket; + } + + protected function send(string $chunk): void + { + socket_sendto($this->getSocket(), $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); } - protected function assembleMessage($line, $header) + protected function assembleMessage(string $line, string $header): string { - $chunkSize = self::DATAGRAM_MAX_LENGTH - strlen($header); + $chunkSize = static::DATAGRAM_MAX_LENGTH - strlen($header); - return $header . substr($line, 0, $chunkSize); + return $header . Utils::substr($line, 0, $chunkSize); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php index 4dfd5f5ec..deaa19f80 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -1,4 +1,4 @@ - */ private $dateFormats = array( self::RFC3164 => 'M d H:i:s', self::RFC5424 => \DateTime::RFC3339, + self::RFC5424e => \DateTime::RFC3339_EXTENDED, ); + /** @var UdpSocket */ protected $socket; + /** @var string */ protected $ident; + /** @var self::RFC* */ protected $rfc; /** - * @param string $host - * @param int $port - * @param mixed $facility - * @param 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. + * @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 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. + * @throws MissingExtensionException + * + * @phpstan-param self::RFC* $rfc */ - public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $ident = 'php', $rfc = self::RFC5424) + public function __construct(string $host, int $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, string $ident = 'php', int $rfc = self::RFC5424) { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler'); + } + parent::__construct($facility, $level, $bubble); $this->ident = $ident; $this->rfc = $rfc; - $this->socket = new UdpSocket($host, $port ?: 514); + $this->socket = new UdpSocket($host, $port); } - protected function write(array $record) + protected function write(array $record): void { $lines = $this->splitMessageIntoLines($record['formatted']); - $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']]); + $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']], $record['datetime']); foreach ($lines as $line) { $this->socket->write($line, $header); } } - public function close() + public function close(): void { $this->socket->close(); } - private function splitMessageIntoLines($message) + /** + * @param string|string[] $message + * @return string[] + */ + private function splitMessageIntoLines($message): array { if (is_array($message)) { $message = implode("\n", $message); } - return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY); + $lines = preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); + if (false === $lines) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $lines; } /** * Make common syslog header (see rfc5424 or rfc3164) */ - protected function makeCommonSyslogHeader($severity) + protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime): string { $priority = $severity + $this->facility; @@ -93,32 +117,34 @@ protected function makeCommonSyslogHeader($severity) $hostname = '-'; } - $date = $this->getDateTime(); - if ($this->rfc === self::RFC3164) { + // see https://github.com/phpstan/phpstan/issues/5348 + // @phpstan-ignore-next-line + $dateNew = $datetime->setTimezone(new \DateTimeZone('UTC')); + $date = $dateNew->format($this->dateFormats[$this->rfc]); + return "<$priority>" . $date . " " . $hostname . " " . $this->ident . "[" . $pid . "]: "; - } else { - return "<$priority>1 " . - $date . " " . - $hostname . " " . - $this->ident . " " . - $pid . " - - "; } - } - protected function getDateTime() - { - return date($this->dateFormats[$this->rfc]); + $date = $datetime->format($this->dateFormats[$this->rfc]); + + return "<$priority>1 " . + $date . " " . + $hostname . " " . + $this->ident . " " . + $pid . " - - "; } /** * Inject your own socket, mainly used for testing */ - public function setSocket($socket) + public function setSocket(UdpSocket $socket): self { $this->socket = $socket; + + return $this; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php new file mode 100644 index 000000000..0b1ac71bf --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php @@ -0,0 +1,278 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RuntimeException; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Handler send logs to Telegram using Telegram Bot API. + * + * How to use: + * 1) Create telegram bot with https://telegram.me/BotFather + * 2) Create a telegram channel where logs will be recorded. + * 3) Add created bot from step 1 to the created channel from step 2. + * + * Use telegram bot API key from step 1 and channel name with '@' prefix from step 2 to create instance of TelegramBotHandler + * + * @link https://core.telegram.org/bots/api + * + * @author Mazur Alexandr + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class TelegramBotHandler extends AbstractProcessingHandler +{ + private const BOT_API = 'https://api.telegram.org/bot'; + + /** + * The available values of parseMode according to the Telegram api documentation + */ + private const AVAILABLE_PARSE_MODES = [ + 'HTML', + 'MarkdownV2', + 'Markdown', // legacy mode without underline and strikethrough, use MarkdownV2 instead + ]; + + /** + * The maximum number of characters allowed in a message according to the Telegram api documentation + */ + private const MAX_MESSAGE_LENGTH = 4096; + + /** + * Telegram bot access token provided by BotFather. + * Create telegram bot with https://telegram.me/BotFather and use access token from it. + * @var string + */ + private $apiKey; + + /** + * Telegram channel name. + * Since to start with '@' symbol as prefix. + * @var string + */ + private $channel; + + /** + * The kind of formatting that is used for the message. + * See available options at https://core.telegram.org/bots/api#formatting-options + * or in AVAILABLE_PARSE_MODES + * @var ?string + */ + private $parseMode; + + /** + * Disables link previews for links in the message. + * @var ?bool + */ + private $disableWebPagePreview; + + /** + * Sends the message silently. Users will receive a notification with no sound. + * @var ?bool + */ + private $disableNotification; + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + * @var bool + */ + private $splitLongMessages; + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + * @var bool + */ + private $delayBetweenMessages; + + /** + * @param string $apiKey Telegram bot access token provided by BotFather + * @param string $channel Telegram channel name + * @param bool $splitLongMessages Split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages + * @param bool $delayBetweenMessages Adds delay between sending a split message according to Telegram API + * @throws MissingExtensionException + */ + public function __construct( + string $apiKey, + string $channel, + $level = Logger::DEBUG, + bool $bubble = true, + ?string $parseMode = null, + ?bool $disableWebPagePreview = null, + ?bool $disableNotification = null, + bool $splitLongMessages = false, + bool $delayBetweenMessages = false + ) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler'); + } + + parent::__construct($level, $bubble); + + $this->apiKey = $apiKey; + $this->channel = $channel; + $this->setParseMode($parseMode); + $this->disableWebPagePreview($disableWebPagePreview); + $this->disableNotification($disableNotification); + $this->splitLongMessages($splitLongMessages); + $this->delayBetweenMessages($delayBetweenMessages); + } + + 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) . '.'); + } + + $this->parseMode = $parseMode; + + return $this; + } + + public function disableWebPagePreview(?bool $disableWebPagePreview = null): self + { + $this->disableWebPagePreview = $disableWebPagePreview; + + return $this; + } + + public function disableNotification(?bool $disableNotification = null): self + { + $this->disableNotification = $disableNotification; + + return $this; + } + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + * @param bool $splitLongMessages + * @return $this + */ + public function splitLongMessages(bool $splitLongMessages = false): self + { + $this->splitLongMessages = $splitLongMessages; + + return $this; + } + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + * @param bool $delayBetweenMessages + * @return $this + */ + public function delayBetweenMessages(bool $delayBetweenMessages = false): self + { + $this->delayBetweenMessages = $delayBetweenMessages; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + /** @var Record[] $messages */ + $messages = []; + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $messages[] = $record; + } + + if (!empty($messages)) { + $this->send((string)$this->getFormatter()->formatBatch($messages)); + } + } + + /** + * @inheritDoc + */ + protected function write(array $record): void + { + $this->send($record['formatted']); + } + + /** + * Send request to @link https://api.telegram.org/bot on SendMessage action. + * @param string $message + */ + protected function send(string $message): void + { + $messages = $this->handleMessageLength($message); + + foreach ($messages as $key => $msg) { + if ($this->delayBetweenMessages && $key > 0) { + sleep(1); + } + + $this->sendCurl($msg); + } + } + + protected function sendCurl(string $message): void + { + if ('' === trim($message)) { + return; + } + + $ch = curl_init(); + $url = self::BOT_API . $this->apiKey . '/SendMessage'; + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'text' => $message, + 'chat_id' => $this->channel, + 'parse_mode' => $this->parseMode, + 'disable_web_page_preview' => $this->disableWebPagePreview, + 'disable_notification' => $this->disableNotification, + ])); + + $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) { + throw new RuntimeException('Telegram API error. Description: ' . $result['description']); + } + } + + /** + * Handle a message that is too long: truncates or splits into several + * @param string $message + * @return string[] + */ + private function handleMessageLength(string $message): array + { + $truncatedMarker = ' (...truncated)'; + if (!$this->splitLongMessages && strlen($message) > self::MAX_MESSAGE_LENGTH) { + return [Utils::substr($message, 0, self::MAX_MESSAGE_LENGTH - strlen($truncatedMarker)) . $truncatedMarker]; + } + + return str_split($message, self::MAX_MESSAGE_LENGTH); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php index 478db0ac0..0986da270 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -1,4 +1,4 @@ - */ + protected $recordsByLevel = []; + /** @var bool */ private $skipReset = false; + /** + * @return array + * + * @phpstan-return Record[] + */ public function getRecords() { return $this->records; } + /** + * @return void + */ public function clear() { - $this->records = array(); - $this->recordsByLevel = array(); + $this->records = []; + $this->recordsByLevel = []; } + /** + * @return void + */ public function reset() { if (!$this->skipReset) { @@ -87,21 +108,32 @@ public function reset() } } - public function setSkipReset($skipReset) + /** + * @return void + */ + public function setSkipReset(bool $skipReset) { $this->skipReset = $skipReset; } - public function hasRecords($level) + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecords($level): bool { - return isset($this->recordsByLevel[$level]); + return isset($this->recordsByLevel[Logger::toMonologLevel($level)]); } /** * @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records - * @param int $level Logger::LEVEL constant value + * @param string|int $level Logging level value or name + * + * @phpstan-param array{message: string, context?: mixed[]}|string $record + * @phpstan-param Level|LevelName|LogLevel::* $level */ - public function hasRecord($record, $level) + public function hasRecord($record, $level): bool { if (is_string($record)) { $record = array('message' => $record); @@ -114,36 +146,52 @@ public function hasRecord($record, $level) if (isset($record['context']) && $rec['context'] !== $record['context']) { return false; } + return true; }, $level); } - public function hasRecordThatContains($message, $level) + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatContains(string $message, $level): bool { return $this->hasRecordThatPasses(function ($rec) use ($message) { return strpos($rec['message'], $message) !== false; }, $level); } - public function hasRecordThatMatches($regex, $level) + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatMatches(string $regex, $level): bool { - return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return $this->hasRecordThatPasses(function (array $rec) use ($regex): bool { return preg_match($regex, $rec['message']) > 0; }, $level); } - public function hasRecordThatPasses($predicate, $level) + /** + * @param string|int $level Logging level value or name + * @return bool + * + * @psalm-param callable(Record, int): mixed $predicate + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatPasses(callable $predicate, $level) { - if (!is_callable($predicate)) { - throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds"); - } + $level = Logger::toMonologLevel($level); if (!isset($this->recordsByLevel[$level])) { return false; } foreach ($this->recordsByLevel[$level] as $i => $rec) { - if (call_user_func($predicate, $rec, $i)) { + if ($predicate($rec, $i)) { return true; } } @@ -152,23 +200,29 @@ public function hasRecordThatPasses($predicate, $level) } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->recordsByLevel[$record['level']][] = $record; $this->records[] = $record; } + /** + * @param string $method + * @param mixed[] $args + * @return bool + */ public function __call($method, $args) { 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(array($this, $genericMethod), $args); + return call_user_func_array($callback, $args); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php new file mode 100644 index 000000000..c81835288 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +trait WebRequestRecognizerTrait +{ + /** + * Checks if PHP's serving a web request + * @return bool + */ + protected function isWebRequest(): bool + { + return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php index 7d7622a39..b6d3d3b19 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ class WhatFailureGroupHandler extends GroupHandler { /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { try { $handler->handle($record); - } catch (\Exception $e) { - // What failure? } catch (\Throwable $e) { // What failure? } @@ -44,26 +43,36 @@ public function handle(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { if ($this->processors) { $processed = array(); foreach ($records as $record) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } - $processed[] = $record; + $processed[] = $this->processRecord($record); } + /** @var Record[] $records */ $records = $processed; } foreach ($this->handlers as $handler) { try { $handler->handleBatch($records); - } catch (\Exception $e) { + } catch (\Throwable $e) { // What failure? + } + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + foreach ($this->handlers as $handler) { + try { + $handler->close(); } catch (\Throwable $e) { // What failure? } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php index a20aeae01..ddd46d8c5 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -1,4 +1,5 @@ - * @author Jason Davis + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class ZendMonitorHandler extends AbstractProcessingHandler { /** * Monolog level / ZendMonitor Custom Event priority map * - * @var array + * @var array */ - protected $levelMap = array(); + protected $levelMap = []; /** - * Construct - * - * @param int $level - * @param bool $bubble * @throws MissingExtensionException */ - public function __construct($level = Logger::DEBUG, $bubble = true) + public function __construct($level = Logger::DEBUG, bool $bubble = true) { if (!function_exists('zend_monitor_custom_event')) { throw new MissingExtensionException( @@ -43,7 +43,7 @@ public function __construct($level = Logger::DEBUG, $bubble = true) ); } //zend monitor constants are not defined if zend monitor is not enabled. - $this->levelMap = array( + $this->levelMap = [ Logger::DEBUG => \ZEND_MONITOR_EVENT_SEVERITY_INFO, Logger::INFO => \ZEND_MONITOR_EVENT_SEVERITY_INFO, Logger::NOTICE => \ZEND_MONITOR_EVENT_SEVERITY_INFO, @@ -52,14 +52,14 @@ public function __construct($level = Logger::DEBUG, $bubble = true) Logger::CRITICAL => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, Logger::ALERT => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, Logger::EMERGENCY => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, - ); + ]; parent::__construct($level, $bubble); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->writeZendMonitorCustomEvent( Logger::getLevelName($record['level']), @@ -71,30 +71,30 @@ protected function write(array $record) /** * Write to Zend Monitor Events - * @param string $type Text displayed in "Class Name (custom)" field - * @param string $message Text displayed in "Error String" - * @param mixed $formatted Displayed in Custom Variables tab - * @param int $severity Set the event severity level (-1,0,1) + * @param string $type Text displayed in "Class Name (custom)" field + * @param string $message Text displayed in "Error String" + * @param array $formatted Displayed in Custom Variables tab + * @param int $severity Set the event severity level (-1,0,1) + * + * @phpstan-param FormattedRecord $formatted */ - protected function writeZendMonitorCustomEvent($type, $message, $formatted, $severity) + protected function writeZendMonitorCustomEvent(string $type, string $message, array $formatted, int $severity): void { zend_monitor_custom_event($type, $message, $formatted, $severity); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getDefaultFormatter() + public function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter(); } /** - * Get the level map - * - * @return array + * @return array */ - public function getLevelMap() + public function getLevelMap(): array { return $this->levelMap; } diff --git a/vendor/monolog/monolog/src/Monolog/LogRecord.php b/vendor/monolog/monolog/src/Monolog/LogRecord.php new file mode 100644 index 000000000..702807d71 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/LogRecord.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use ArrayAccess; + +/** + * Monolog log record interface for forward compatibility with Monolog 3.0 + * + * This is just present in Monolog 2.4+ to allow interoperable code to be written against + * both versions by type-hinting arguments as `array|\Monolog\LogRecord $record` + * + * Do not rely on this interface for other purposes, and do not implement it. + * + * @author Jordi Boggiano + * @template-extends \ArrayAccess<'message'|'level'|'context'|'level_name'|'channel'|'datetime'|'extra'|'formatted', mixed> + * @phpstan-import-type Record from Logger + */ +interface LogRecord extends \ArrayAccess +{ + /** + * @phpstan-return Record + */ + public function toArray(): array; +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php index 7d26b2916..bf65d3c53 100644 --- a/vendor/monolog/monolog/src/Monolog/Logger.php +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -1,4 +1,4 @@ - + * + * @phpstan-type Level Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY + * @phpstan-type LevelName 'DEBUG'|'INFO'|'NOTICE'|'WARNING'|'ERROR'|'CRITICAL'|'ALERT'|'EMERGENCY' + * @phpstan-type Record array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[]} */ class Logger implements LoggerInterface, ResettableInterface { /** * Detailed debug information */ - const DEBUG = 100; + public const DEBUG = 100; /** * Interesting events * * Examples: User logs in, SQL logs. */ - const INFO = 200; + public const INFO = 200; /** * Uncommon events */ - const NOTICE = 250; + public const NOTICE = 250; /** * Exceptional occurrences that are not errors @@ -50,19 +56,19 @@ class Logger implements LoggerInterface, ResettableInterface * Examples: Use of deprecated APIs, poor use of an API, * undesirable things that are not necessarily wrong. */ - const WARNING = 300; + public const WARNING = 300; /** * Runtime errors */ - const ERROR = 400; + public const ERROR = 400; /** * Critical conditions * * Example: Application component unavailable, unexpected exception. */ - const CRITICAL = 500; + public const CRITICAL = 500; /** * Action must be taken immediately @@ -70,12 +76,12 @@ class Logger implements LoggerInterface, ResettableInterface * Example: Entire website down, database unavailable, etc. * This should trigger the SMS alerts and wake you up. */ - const ALERT = 550; + public const ALERT = 550; /** * Urgent alert. */ - const EMERGENCY = 600; + public const EMERGENCY = 600; /** * Monolog API version @@ -85,14 +91,16 @@ class Logger implements LoggerInterface, ResettableInterface * * @var int */ - const API = 1; + public const API = 2; /** - * Logging levels from syslog protocol defined in RFC 5424 + * This is a static variable and not a constant to serve as an extension point for custom levels + * + * @var array $levels Logging levels with the levels as key * - * @var array $levels Logging levels + * @phpstan-var array $levels Logging levels with the levels as key */ - protected static $levels = array( + protected static $levels = [ self::DEBUG => 'DEBUG', self::INFO => 'INFO', self::NOTICE => 'NOTICE', @@ -101,12 +109,23 @@ class Logger implements LoggerInterface, ResettableInterface self::CRITICAL => 'CRITICAL', self::ALERT => 'ALERT', self::EMERGENCY => 'EMERGENCY', - ); + ]; /** - * @var \DateTimeZone + * Mapping between levels numbers defined in RFC 5424 and Monolog ones + * + * @phpstan-var array $rfc_5424_levels */ - protected static $timezone; + 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 @@ -135,36 +154,64 @@ class Logger implements LoggerInterface, ResettableInterface protected $microsecondTimestamps = true; /** - * @var callable + * @var DateTimeZone + */ + protected $timezone; + + /** + * @var callable|null */ protected $exceptionHandler; /** - * @param string $name The logging channel + * @var int Keeps track of depth to prevent infinite logging loops + */ + private $logDepth = 0; + + /** + * @var \WeakMap<\Fiber, int> 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 $processors + * + * @param string $name The logging channel, a simple descriptive name that is attached to all log records * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. * @param callable[] $processors Optional array of processors + * @param DateTimeZone|null $timezone Optional timezone, if not provided date_default_timezone_get() will be used */ - public function __construct($name, array $handlers = array(), array $processors = array()) + public function __construct(string $name, array $handlers = [], array $processors = [], ?DateTimeZone $timezone = null) { $this->name = $name; $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; + } } - /** - * @return string - */ - public function getName() + public function getName(): string { return $this->name; } /** * Return a new cloned instance with the name changed - * - * @return static */ - public function withName($name) + public function withName(string $name): self { $new = clone $this; $new->name = $name; @@ -174,11 +221,8 @@ public function withName($name) /** * Pushes a handler on to the stack. - * - * @param HandlerInterface $handler - * @return $this */ - public function pushHandler(HandlerInterface $handler) + public function pushHandler(HandlerInterface $handler): self { array_unshift($this->handlers, $handler); @@ -188,9 +232,9 @@ public function pushHandler(HandlerInterface $handler) /** * Pops a handler from the stack * - * @return HandlerInterface + * @throws \LogicException If empty handler stack */ - public function popHandler() + public function popHandler(): HandlerInterface { if (!$this->handlers) { throw new \LogicException('You tried to pop from an empty handler stack.'); @@ -204,12 +248,11 @@ public function popHandler() * * If a map is passed, keys will be ignored. * - * @param HandlerInterface[] $handlers - * @return $this + * @param HandlerInterface[] $handlers */ - public function setHandlers(array $handlers) + public function setHandlers(array $handlers): self { - $this->handlers = array(); + $this->handlers = []; foreach (array_reverse($handlers) as $handler) { $this->pushHandler($handler); } @@ -220,22 +263,16 @@ public function setHandlers(array $handlers) /** * @return HandlerInterface[] */ - public function getHandlers() + public function getHandlers(): array { return $this->handlers; } /** * Adds a processor on to the stack. - * - * @param callable $callback - * @return $this */ - public function pushProcessor($callback) + public function pushProcessor(callable $callback): self { - if (!is_callable($callback)) { - throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); - } array_unshift($this->processors, $callback); return $this; @@ -244,9 +281,10 @@ public function pushProcessor($callback) /** * Removes the processor on top of the stack and returns it. * + * @throws \LogicException If empty processor stack * @return callable */ - public function popProcessor() + public function popProcessor(): callable { if (!$this->processors) { throw new \LogicException('You tried to pop from an empty processor stack.'); @@ -258,7 +296,7 @@ public function popProcessor() /** * @return callable[] */ - public function getProcessors() + public function getProcessors(): array { return $this->processors; } @@ -267,91 +305,118 @@ public function getProcessors() * Control the use of microsecond resolution timestamps in the 'datetime' * member of new records. * - * Generating microsecond resolution timestamps by calling - * microtime(true), formatting the result via sprintf() and then parsing - * the resulting string via \DateTime::createFromFormat() can incur - * a measurable runtime overhead vs simple usage of DateTime to capture - * a second resolution timestamp in systems which generate a large number - * of log events. + * As of PHP7.1 microseconds are always included by the engine, so + * there is no performance penalty and Monolog 2 enabled microseconds + * by default. This function lets you disable them though in case you want + * to suppress microseconds from the output. * * @param bool $micro True to use microtime() to create timestamps */ - public function useMicrosecondTimestamps($micro) + public function useMicrosecondTimestamps(bool $micro): self { - $this->microsecondTimestamps = (bool) $micro; + $this->microsecondTimestamps = $micro; + + 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 array $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($level, $message, array $context = array()) + public function addRecord(int $level, string $message, array $context = [], ?DateTimeImmutable $datetime = null): bool { - if (!$this->handlers) { - $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + if (isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; } - $levelName = static::getLevelName($level); - - // check if any handler will handle this message so we can return early and save cycles - $handlerKey = null; - reset($this->handlers); - while ($handler = current($this->handlers)) { - if ($handler->isHandling(array('level' => $level))) { - $handlerKey = key($this->handlers); - break; + if ($this->detectCycles) { + if (\PHP_VERSION_ID >= 80100 && $fiber = \Fiber::getCurrent()) { + // @phpstan-ignore offsetAssign.dimType + $this->fiberLogDepth[$fiber] = $this->fiberLogDepth[$fiber] ?? 0; + $logDepth = ++$this->fiberLogDepth[$fiber]; + } else { + $logDepth = ++$this->logDepth; } - - next($this->handlers); + } else { + $logDepth = 0; } - if (null === $handlerKey) { + 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; } - if (!static::$timezone) { - static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); - } - - // php7.1+ always has microseconds enabled, so we do not need this hack - if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { - $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); - } else { - $ts = new \DateTime('now', static::$timezone); - } - $ts->setTimezone(static::$timezone); + 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; + } + } - $record = array( - 'message' => (string) $message, - 'context' => $context, - 'level' => $level, - 'level_name' => $levelName, - 'channel' => $this->name, - 'datetime' => $ts, - 'extra' => array(), - ); + // 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; + } + } catch (Throwable $e) { + $this->handleException($e, $record); - try { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); + return true; + } } - - while ($handler = current($this->handlers)) { - if (true === $handler->handle($record)) { - break; + } finally { + if ($this->detectCycles) { + if (isset($fiber)) { + $this->fiberLogDepth[$fiber]--; + } else { + $this->logDepth--; } - - next($this->handlers); } - } catch (Exception $e) { - $this->handleException($e, $record); } - return true; + return null !== $record; } /** @@ -364,12 +429,10 @@ public function addRecord($level, $message, array $context = array()) * This is useful at the end of a request and will be called automatically on every handler * when they get destructed. */ - public function close() + public function close(): void { foreach ($this->handlers as $handler) { - if (method_exists($handler, 'close')) { - $handler->close(); - } + $handler->close(); } } @@ -383,7 +446,7 @@ public function close() * have a long running process like a worker or an application server serving multiple requests * in one process. */ - public function reset() + public function reset(): void { foreach ($this->handlers as $handler) { if ($handler instanceof ResettableInterface) { @@ -398,108 +461,13 @@ public function reset() } } - /** - * Adds a log record at the DEBUG level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addDebug($message, array $context = array()) - { - return $this->addRecord(static::DEBUG, $message, $context); - } - - /** - * Adds a log record at the INFO level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addInfo($message, array $context = array()) - { - return $this->addRecord(static::INFO, $message, $context); - } - - /** - * Adds a log record at the NOTICE level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addNotice($message, array $context = array()) - { - return $this->addRecord(static::NOTICE, $message, $context); - } - - /** - * Adds a log record at the WARNING level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addWarning($message, array $context = array()) - { - return $this->addRecord(static::WARNING, $message, $context); - } - - /** - * Adds a log record at the ERROR level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addError($message, array $context = array()) - { - return $this->addRecord(static::ERROR, $message, $context); - } - - /** - * Adds a log record at the CRITICAL level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addCritical($message, array $context = array()) - { - return $this->addRecord(static::CRITICAL, $message, $context); - } - - /** - * Adds a log record at the ALERT level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addAlert($message, array $context = array()) - { - return $this->addRecord(static::ALERT, $message, $context); - } - - /** - * Adds a log record at the EMERGENCY level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addEmergency($message, array $context = array()) - { - return $this->addRecord(static::EMERGENCY, $message, $context); - } - /** * Gets all supported logging levels. * - * @return array Assoc array with human-readable level names => level codes. + * @return array Assoc array with human-readable level names => level codes. + * @phpstan-return array */ - public static function getLevels() + public static function getLevels(): array { return array_flip(static::$levels); } @@ -507,10 +475,12 @@ public static function getLevels() /** * Gets the name of the logging level. * - * @param int $level - * @return string + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param Level $level + * @phpstan-return LevelName */ - public static function getLevelName($level) + public static function getLevelName(int $level): string { if (!isset(static::$levels[$level])) { throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); @@ -522,18 +492,32 @@ public static function getLevelName($level) /** * Converts PSR-3 levels to Monolog ones if necessary * - * @param string|int $level Level number (monolog) or name (PSR-3) - * @return int + * @param string|int $level Level number (monolog) or name (PSR-3) + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param Level|LevelName|LogLevel::* $level + * @phpstan-return Level */ - public static function toMonologLevel($level) + public static function toMonologLevel($level): int { if (is_string($level)) { + if (is_numeric($level)) { + /** @phpstan-ignore-next-line */ + return intval($level); + } + // Contains chars of all log levels and avoids using strtoupper() which may have - // strange results depending on locale (for example, "i" will become "İ") + // strange results depending on locale (for example, "i" will become "İ" in Turkish locale) $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY'); if (defined(__CLASS__.'::'.$upper)) { return constant(__CLASS__ . '::' . $upper); } + + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels)); + } + + if (!is_int($level)) { + throw new InvalidArgumentException('Level "'.var_export($level, true).'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels)); } return $level; @@ -542,14 +526,13 @@ public static function toMonologLevel($level) /** * Checks whether the Logger has a handler that listens on the given level * - * @param int $level - * @return bool + * @phpstan-param Level $level */ - public function isHandling($level) + public function isHandling(int $level): bool { - $record = array( + $record = [ 'level' => $level, - ); + ]; foreach ($this->handlers as $handler) { if ($handler->isHandling($record)) { @@ -561,57 +544,46 @@ public function isHandling($level) } /** - * Set a custom exception handler + * Set a custom exception handler that will be called if adding a new record fails * - * @param callable $callback - * @return $this + * The callable will receive an exception object and the record that failed to be logged */ - public function setExceptionHandler($callback) + public function setExceptionHandler(?callable $callback): self { - if (!is_callable($callback)) { - throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given'); - } $this->exceptionHandler = $callback; return $this; } - /** - * @return callable - */ - public function getExceptionHandler() + public function getExceptionHandler(): ?callable { return $this->exceptionHandler; } - /** - * Delegates exception management to the custom exception handler, - * or throws the exception if no custom handler is set. - */ - protected function handleException(Exception $e, array $record) - { - if (!$this->exceptionHandler) { - throw $e; - } - - call_user_func($this->exceptionHandler, $e, $record); - } - /** * Adds a log record at an arbitrary level. * * This method allows for compatibility with common interfaces. * - * @param mixed $level The log level - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @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 + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ - public function log($level, $message, array $context = array()) + public function log($level, $message, array $context = []): void { + if (!is_int($level) && !is_string($level)) { + 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); - return $this->addRecord($level, $message, $context); + $this->addRecord($level, (string) $message, $context); } /** @@ -619,13 +591,12 @@ public function log($level, $message, array $context = array()) * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function debug($message, array $context = array()) + public function debug($message, array $context = []): void { - return $this->addRecord(static::DEBUG, $message, $context); + $this->addRecord(static::DEBUG, (string) $message, $context); } /** @@ -633,13 +604,12 @@ public function debug($message, array $context = array()) * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function info($message, array $context = array()) + public function info($message, array $context = []): void { - return $this->addRecord(static::INFO, $message, $context); + $this->addRecord(static::INFO, (string) $message, $context); } /** @@ -647,13 +617,12 @@ public function info($message, array $context = array()) * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function notice($message, array $context = array()) + public function notice($message, array $context = []): void { - return $this->addRecord(static::NOTICE, $message, $context); + $this->addRecord(static::NOTICE, (string) $message, $context); } /** @@ -661,136 +630,133 @@ public function notice($message, array $context = array()) * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function warn($message, array $context = array()) + public function warning($message, array $context = []): void { - return $this->addRecord(static::WARNING, $message, $context); + $this->addRecord(static::WARNING, (string) $message, $context); } /** - * Adds a log record at the WARNING level. + * Adds a log record at the ERROR level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function warning($message, array $context = array()) + public function error($message, array $context = []): void { - return $this->addRecord(static::WARNING, $message, $context); + $this->addRecord(static::ERROR, (string) $message, $context); } /** - * Adds a log record at the ERROR level. + * Adds a log record at the CRITICAL level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function err($message, array $context = array()) + public function critical($message, array $context = []): void { - return $this->addRecord(static::ERROR, $message, $context); + $this->addRecord(static::CRITICAL, (string) $message, $context); } /** - * Adds a log record at the ERROR level. + * Adds a log record at the ALERT level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function error($message, array $context = array()) + public function alert($message, array $context = []): void { - return $this->addRecord(static::ERROR, $message, $context); + $this->addRecord(static::ALERT, (string) $message, $context); } /** - * Adds a log record at the CRITICAL level. + * Adds a log record at the EMERGENCY level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function crit($message, array $context = array()) + public function emergency($message, array $context = []): void { - return $this->addRecord(static::CRITICAL, $message, $context); + $this->addRecord(static::EMERGENCY, (string) $message, $context); } /** - * Adds a log record at the CRITICAL level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * Sets the timezone to be used for the timestamp of log records. */ - public function critical($message, array $context = array()) + public function setTimezone(DateTimeZone $tz): self { - return $this->addRecord(static::CRITICAL, $message, $context); + $this->timezone = $tz; + + return $this; } /** - * Adds a log record at the ALERT level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * Returns the timezone to be used for the timestamp of log records. */ - public function alert($message, array $context = array()) + public function getTimezone(): DateTimeZone { - return $this->addRecord(static::ALERT, $message, $context); + return $this->timezone; } /** - * Adds a log record at the EMERGENCY level. - * - * This method allows for compatibility with common interfaces. + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param array $record + * @phpstan-param Record $record */ - public function emerg($message, array $context = array()) + protected function handleException(Throwable $e, array $record): void { - return $this->addRecord(static::EMERGENCY, $message, $context); + if (!$this->exceptionHandler) { + throw $e; + } + + ($this->exceptionHandler)($e, $record); } /** - * Adds a log record at the EMERGENCY level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @return array */ - public function emergency($message, array $context = array()) + public function __serialize(): array { - return $this->addRecord(static::EMERGENCY, $message, $context); + 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, + ]; } /** - * Set the timezone to be used for the timestamp of log records. - * - * This is stored globally for all Logger instances - * - * @param \DateTimeZone $tz Timezone object + * @param array $data */ - public static function setTimezone(\DateTimeZone $tz) + public function __unserialize(array $data): void { - self::$timezone = $tz; + 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; + } } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php index cdf5ec736..6f6185950 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class GitProcessor implements ProcessorInterface { + /** @var int */ private $level; - private static $cache; + /** @var array{branch: string, commit: string}|array|null */ + private static $cache = null; + /** + * @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) { $this->level = Logger::toMonologLevel($level); } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { @@ -45,20 +55,23 @@ public function __invoke(array $record) return $record; } - private static function getGitInfo() + /** + * @return array{branch: string, commit: string}|array + */ + private static function getGitInfo(): array { if (self::$cache) { return self::$cache; } - $branches = `git branch -v --no-abbrev`; + $branches = shell_exec('git branch -v --no-abbrev'); if ($branches && preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { - return self::$cache = array( + return self::$cache = [ 'branch' => $matches[1], 'commit' => $matches[2], - ); + ]; } - return self::$cache = array(); + return self::$cache = []; } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php new file mode 100644 index 000000000..91fda7d6d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects value of gethostname in all records + */ +class HostnameProcessor implements ProcessorInterface +{ + /** @var string */ + private static $host; + + public function __construct() + { + self::$host = (string) gethostname(); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['hostname'] = self::$host; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php index 6ae192a23..a32e76b21 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class IntrospectionProcessor implements ProcessorInterface { + /** @var int */ private $level; - + /** @var string[] */ private $skipClassesPartials; - + /** @var int */ private $skipStackFramesCount; - - private $skipFunctions = array( + /** @var string[] */ + private $skipFunctions = [ 'call_user_func', 'call_user_func_array', - ); + ]; - public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0) + /** + * @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) { $this->level = Logger::toMonologLevel($level); - $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials); + $this->skipClassesPartials = array_merge(['Monolog\\'], $skipClassesPartials); $this->skipStackFramesCount = $skipStackFramesCount; } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { return $record; } - /* - * http://php.net/manual/en/function.debug-backtrace.php - * As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. - * Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. - */ - $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); // skip first since it's always the current method array_shift($trace); @@ -74,11 +79,13 @@ public function __invoke(array $record) foreach ($this->skipClassesPartials as $part) { if (strpos($trace[$i]['class'], $part) !== false) { $i++; + continue 2; } } } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { $i++; + continue; } @@ -90,18 +97,22 @@ public function __invoke(array $record) // we should have the call source now $record['extra'] = array_merge( $record['extra'], - array( + [ 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'callType' => isset($trace[$i]['type']) ? $trace[$i]['type'] : null, 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, - ) + ] ); return $record; } - private function isTraceClassOrSkippedFunction(array $trace, $index) + /** + * @param array[] $trace + */ + private function isTraceClassOrSkippedFunction(array $trace, int $index): bool { if (!isset($trace[$index])) { return false; diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php index 0543e9292..37c756fcb 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -1,4 +1,4 @@ -realUsage); - $formatted = $this->formatBytes($bytes); + $usage = memory_get_peak_usage($this->realUsage); - $record['extra']['memory_peak_usage'] = $formatted; + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record['extra']['memory_peak_usage'] = $usage; return $record; } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php index 2a379a302..227deb7c8 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -1,4 +1,4 @@ -realUsage = (bool) $realUsage; - $this->useFormatting = (bool) $useFormatting; + $this->realUsage = $realUsage; + $this->useFormatting = $useFormatting; } /** * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is * * @param int $bytes - * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int */ - protected function formatBytes($bytes) + protected function formatBytes(int $bytes) { - $bytes = (int) $bytes; - if (!$this->useFormatting) { return $bytes; } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php index 2783d656b..e141921e9 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -1,4 +1,4 @@ -realUsage); - $formatted = $this->formatBytes($bytes); + $usage = memory_get_usage($this->realUsage); - $record['extra']['memory_usage'] = $formatted; + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record['extra']['memory_usage'] = $usage; return $record; } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php index 2f5b32659..239e4c8fd 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -1,9 +1,9 @@ - + * (c) Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,27 +12,37 @@ namespace Monolog\Processor; use Monolog\Logger; +use Psr\Log\LogLevel; /** * Injects Hg branch and Hg revision number in all records * * @author Jonathan A. Schweder + * + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ class MercurialProcessor implements ProcessorInterface { + /** @var Level */ private $level; - private static $cache; + /** @var array{branch: string, revision: string}|array|null */ + private static $cache = null; + /** + * @param int|string $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ public function __construct($level = Logger::DEBUG) { $this->level = Logger::toMonologLevel($level); } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { @@ -44,20 +54,24 @@ public function __invoke(array $record) return $record; } - private static function getMercurialInfo() + /** + * @return array{branch: string, revision: string}|array + */ + private static function getMercurialInfo(): array { if (self::$cache) { return self::$cache; } - $result = explode(' ', trim(`hg id -nb`)); + $result = explode(' ', trim((string) shell_exec('hg id -nb'))); + if (count($result) >= 3) { - return self::$cache = array( + return self::$cache = [ 'branch' => $result[1], 'revision' => $result[2], - ); + ]; } - return self::$cache = array(); + return self::$cache = []; } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php index 66b80fbbd..3b939a951 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ interface ProcessorInterface { /** - * @return array The processed records + * @return array The processed record + * + * @phpstan-param Record $record + * @phpstan-return Record */ - public function __invoke(array $records); + public function __invoke(array $record); } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php index a318af7e4..e7c12176a 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -1,4 +1,4 @@ -dateFormat = $dateFormat; $this->removeUsedContextFields = $removeUsedContextFields; } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { if (false === strpos($record['message'], '{')) { return $record; } - $replacements = array(); + $replacements = []; foreach ($record['context'] as $key => $val) { $placeholder = '{' . $key . '}'; if (strpos($record['message'], $placeholder) === false) { @@ -59,8 +58,16 @@ public function __invoke(array $record) if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { $replacements[$placeholder] = $val; - } elseif ($val instanceof \DateTime) { - $replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE); + } elseif ($val instanceof \DateTimeInterface) { + if (!$this->dateFormat && $val instanceof \Monolog\DateTimeImmutable) { + // handle monolog dates using __toString if no specific dateFormat was asked for + // so that it follows the useMicroseconds flag + $replacements[$placeholder] = (string) $val; + } 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)) { diff --git a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php index 615a4d991..80f18747a 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -1,4 +1,4 @@ -setTags($tags); } - public function addTags(array $tags = array()) + /** + * @param string[] $tags + */ + public function addTags(array $tags = []): self { $this->tags = array_merge($this->tags, $tags); + + return $this; } - public function setTags(array $tags = array()) + /** + * @param string[] $tags + */ + public function setTags(array $tags = []): self { $this->tags = $tags; + + return $this; } - public function __invoke(array $record) + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array { $record['extra']['tags'] = $this->tags; diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php index d1f708cf2..a27b74dbf 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -1,4 +1,4 @@ - 32 || $length < 1) { + if ($length > 32 || $length < 1) { throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); } - $this->uid = $this->generateUid($length); } - public function __invoke(array $record) + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array { $record['extra']['uid'] = $this->uid; return $record; } - /** - * @return string - */ - public function getUid() + public function getUid(): string { return $this->uid; } @@ -52,8 +52,8 @@ public function reset() $this->uid = $this->generateUid(strlen($this->uid)); } - private function generateUid($length) + private function generateUid(int $length): string { - return substr(hash('md5', uniqid('', true)), 0, $length); + return substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length); } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php index 2e8dfae1b..887f4d396 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -1,4 +1,4 @@ -|\ArrayAccess */ protected $serverData; @@ -28,21 +28,22 @@ class WebProcessor implements ProcessorInterface * * Array is structured as [key in record.extra => key in $serverData] * - * @var array + * @var array */ - protected $extraFields = array( + protected $extraFields = [ 'url' => 'REQUEST_URI', 'ip' => 'REMOTE_ADDR', 'http_method' => 'REQUEST_METHOD', 'server' => 'SERVER_NAME', 'referrer' => 'HTTP_REFERER', - ); + 'user_agent' => 'HTTP_USER_AGENT', + ]; /** - * @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data - * @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer + * @param array|\ArrayAccess|null $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|array|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; @@ -52,28 +53,30 @@ public function __construct($serverData = null, array $extraFields = null) throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); } + $defaultEnabled = ['url', 'ip', 'http_method', 'server', 'referrer']; if (isset($this->serverData['UNIQUE_ID'])) { $this->extraFields['unique_id'] = 'UNIQUE_ID'; + $defaultEnabled[] = 'unique_id'; } - if (null !== $extraFields) { - if (isset($extraFields[0])) { - foreach (array_keys($this->extraFields) as $fieldName) { - if (!in_array($fieldName, $extraFields)) { - unset($this->extraFields[$fieldName]); - } + if (null === $extraFields) { + $extraFields = $defaultEnabled; + } + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields)) { + unset($this->extraFields[$fieldName]); } - } else { - $this->extraFields = $extraFields; } + } else { + $this->extraFields = $extraFields; } } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // skip processing if for some reason request data // is not present (CLI or wonky SAPIs) @@ -86,12 +89,7 @@ public function __invoke(array $record) return $record; } - /** - * @param string $extraName - * @param string $serverName - * @return $this - */ - public function addExtraField($extraName, $serverName) + public function addExtraField(string $extraName, string $serverName): self { $this->extraFields[$extraName] = $serverName; @@ -99,13 +97,13 @@ public function addExtraField($extraName, $serverName) } /** - * @param array $extra - * @return array + * @param mixed[] $extra + * @return mixed[] */ - private function appendExtraFields(array $extra) + private function appendExtraFields(array $extra): array { foreach ($this->extraFields as $extraName => $serverName) { - $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null; + $extra[$extraName] = $this->serverData[$serverName] ?? null; } return $extra; diff --git a/vendor/monolog/monolog/src/Monolog/Registry.php b/vendor/monolog/monolog/src/Monolog/Registry.php index 159b751cd..ae94ae6cc 100644 --- a/vendor/monolog/monolog/src/Monolog/Registry.php +++ b/vendor/monolog/monolog/src/Monolog/Registry.php @@ -1,4 +1,4 @@ -addError('Sent to $api Logger instance'); - * Monolog\Registry::application()->addError('Sent to $application Logger instance'); + * Monolog\Registry::api()->error('Sent to $api Logger instance'); + * Monolog\Registry::application()->error('Sent to $application Logger instance'); * } * * @@ -42,7 +42,7 @@ class Registry * * @var Logger[] */ - private static $loggers = array(); + private static $loggers = []; /** * Adds new logging channel to the registry @@ -51,8 +51,9 @@ class Registry * @param string|null $name Name of the logging channel ($logger->getName() by default) * @param bool $overwrite Overwrite instance in the registry if the given name already exists? * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + * @return void */ - public static function addLogger(Logger $logger, $name = null, $overwrite = false) + public static function addLogger(Logger $logger, ?string $name = null, bool $overwrite = false) { $name = $name ?: $logger->getName(); @@ -68,15 +69,15 @@ public static function addLogger(Logger $logger, $name = null, $overwrite = fals * * @param string|Logger $logger Name or logger instance */ - public static function hasLogger($logger) + public static function hasLogger($logger): bool { if ($logger instanceof Logger) { $index = array_search($logger, self::$loggers, true); return false !== $index; - } else { - return isset(self::$loggers[$logger]); } + + return isset(self::$loggers[$logger]); } /** @@ -84,7 +85,7 @@ public static function hasLogger($logger) * * @param string|Logger $logger Name or logger instance */ - public static function removeLogger($logger) + public static function removeLogger($logger): void { if ($logger instanceof Logger) { if (false !== ($idx = array_search($logger, self::$loggers, true))) { @@ -98,9 +99,9 @@ public static function removeLogger($logger) /** * Clears the registry */ - public static function clear() + public static function clear(): void { - self::$loggers = array(); + self::$loggers = []; } /** @@ -108,9 +109,8 @@ public static function clear() * * @param string $name Name of the requested Logger instance * @throws \InvalidArgumentException If named Logger instance is not in the registry - * @return Logger Requested instance of Logger */ - public static function getInstance($name) + public static function getInstance($name): Logger { if (!isset(self::$loggers[$name])) { throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); @@ -123,7 +123,7 @@ public static function getInstance($name) * Gets Logger instance from the registry via static method call * * @param string $name Name of the requested Logger instance - * @param array $arguments Arguments passed to static method call + * @param mixed[] $arguments Arguments passed to static method call * @throws \InvalidArgumentException If named Logger instance is not in the registry * @return Logger Requested instance of Logger */ diff --git a/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php index 635bc77dc..2c5fd7851 100644 --- a/vendor/monolog/monolog/src/Monolog/ResettableInterface.php +++ b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class SignalHandler { + /** @var LoggerInterface */ private $logger; - private $previousSignalHandler = array(); - private $signalLevelMap = array(); - private $signalRestartSyscalls = array(); + /** @var array SIG_DFL, SIG_IGN or previous callable */ + private $previousSignalHandler = []; + /** @var array */ + private $signalLevelMap = []; + /** @var array */ + private $signalRestartSyscalls = []; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } - public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true) + /** + * @param int|string $level Level or level name + * @param bool $callPrevious + * @param bool $restartSyscalls + * @param bool|null $async + * @return $this + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function registerSignalHandler(int $signo, $level = LogLevel::CRITICAL, bool $callPrevious = true, bool $restartSyscalls = true, ?bool $async = true): self { if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { return $this; } + $level = Logger::toMonologLevel($level); + if ($callPrevious) { - if (function_exists('pcntl_signal_get_handler')) { - $handler = pcntl_signal_get_handler($signo); - if ($handler === false) { - return $this; - } - $this->previousSignalHandler[$signo] = $handler; - } else { - $this->previousSignalHandler[$signo] = true; - } + $handler = pcntl_signal_get_handler($signo); + $this->previousSignalHandler[$signo] = $handler; } else { unset($this->previousSignalHandler[$signo]); } $this->signalLevelMap[$signo] = $level; $this->signalRestartSyscalls[$signo] = $restartSyscalls; - if (function_exists('pcntl_async_signals') && $async !== null) { + if ($async !== null) { pcntl_async_signals($async); } - pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); return $this; } - public function handleSignal($signo, array $siginfo = null) + /** + * @param mixed $siginfo + */ + public function handleSignal(int $signo, $siginfo = null): void { - static $signals = array(); + /** @var array $signals */ + static $signals = []; if (!$signals && extension_loaded('pcntl')) { $pcntl = new ReflectionExtension('pcntl'); - $constants = $pcntl->getConstants(); - if (!$constants) { - // HHVM 3.24.2 returns an empty array. - $constants = get_defined_constants(true); - $constants = $constants['Core']; - } - foreach ($constants as $name => $value) { + // HHVM 3.24.2 returns an empty array. + foreach ($pcntl->getConstants() ?: get_defined_constants(true)['Core'] as $name => $value) { if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { $signals[$value] = $name; } } - unset($constants); } - $level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL; - $signal = isset($signals[$signo]) ? $signals[$signo] : $signo; - $context = isset($siginfo) ? $siginfo : array(); + $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL; + $signal = $signals[$signo] ?? $signo; + $context = $siginfo ?? []; $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); if (!isset($this->previousSignalHandler[$signo])) { return; } - if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) { + if ($this->previousSignalHandler[$signo] === SIG_DFL) { if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') - && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) { - $restartSyscalls = isset($this->signalRestartSyscalls[$signo]) ? $this->signalRestartSyscalls[$signo] : true; - pcntl_signal($signo, SIG_DFL, $restartSyscalls); - pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset); - posix_kill(posix_getpid(), $signo); - pcntl_signal_dispatch(); - pcntl_sigprocmask(SIG_SETMASK, $oldset); - pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); - } - } elseif (is_callable($this->previousSignalHandler[$signo])) { - if (PHP_VERSION_ID >= 70100) { - $this->previousSignalHandler[$signo]($signo, $siginfo); - } else { - $this->previousSignalHandler[$signo]($signo); + && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill') + ) { + $restartSyscalls = $this->signalRestartSyscalls[$signo] ?? true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, [$signo], $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); } + } elseif (is_callable($this->previousSignalHandler[$signo])) { + $this->previousSignalHandler[$signo]($signo, $siginfo); } } } diff --git a/vendor/monolog/monolog/src/Monolog/Test/TestCase.php b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php new file mode 100644 index 000000000..bc0b425ea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Test; + +use Monolog\Logger; +use Monolog\DateTimeImmutable; +use Monolog\Formatter\FormatterInterface; + +/** + * Lets you easily generate log records and a dummy formatter for testing purposes + * + * @author Jordi Boggiano + * + * @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 + * + * @return array Record + * + * @phpstan-param Level $level + * @phpstan-return Record + */ + protected function getRecord(int $level = Logger::WARNING, string $message = 'test', array $context = []): array + { + return [ + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => new DateTimeImmutable(true), + 'extra' => [], + ]; + } + + /** + * @phpstan-return Record[] + */ + protected function getMultipleRecords(): array + { + return [ + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error'), + ]; + } + + protected function getIdentityFormatter(): FormatterInterface + { + $formatter = $this->createMock(FormatterInterface::class); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { + return $record['message']; + })); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Utils.php b/vendor/monolog/monolog/src/Monolog/Utils.php index 7f1ba129e..d4ff4c040 100644 --- a/vendor/monolog/monolog/src/Monolog/Utils.php +++ b/vendor/monolog/monolog/src/Monolog/Utils.php @@ -1,4 +1,4 @@ -=')) { - $encodeFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; } if ($ignoreErrors) { @@ -90,16 +104,16 @@ public static function jsonEncode($data, $encodeFlags = null, $ignoreErrors = fa * * If the failure is due to invalid string encoding, try to clean the * input and encode again. If the second encoding attempt fails, the - * inital error is not encoding related or the input can't be cleaned then + * initial error is not encoding related or the input can't be cleaned then * raise a descriptive exception. * - * @param int $code return code of json_last_error function - * @param mixed $data data that was meant to be encoded - * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION * @throws \RuntimeException if failure can't be corrected * @return string JSON encoded data after error correction */ - public static function handleJsonError($code, $data, $encodeFlags = null) + public static function handleJsonError(int $code, $data, ?int $encodeFlags = null): string { if ($code !== JSON_ERROR_UTF8) { self::throwEncodeError($code, $data); @@ -113,8 +127,8 @@ public static function handleJsonError($code, $data, $encodeFlags = null) self::throwEncodeError($code, $data); } - if (null === $encodeFlags && version_compare(PHP_VERSION, '5.4.0', '>=')) { - $encodeFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; } $json = json_encode($data, $encodeFlags); @@ -126,14 +140,35 @@ public static function handleJsonError($code, $data, $encodeFlags = null) return $json; } + /** + * @internal + */ + public static function pcreLastErrorMessage(int $code): string + { + if (PHP_VERSION_ID >= 80000) { + return preg_last_error_msg(); + } + + $constants = (get_defined_constants(true))['pcre']; + $constants = array_filter($constants, function ($key) { + return substr($key, -6) == '_ERROR'; + }, ARRAY_FILTER_USE_KEY); + + $constants = array_flip($constants); + + return $constants[$code] ?? 'UNDEFINED_ERROR'; + } + /** * Throws an exception according to a given code with a customized message * * @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($code, $data) + private static function throwEncodeError(int $code, $data): void { switch ($code) { case JSON_ERROR_DEPTH: @@ -169,21 +204,81 @@ private static function throwEncodeError($code, $data) * can be used as a callback for array_walk_recursive. * * @param mixed $data Input to check and convert if needed, passed by ref - * @private */ - public static function detectAndCleanUtf8(&$data) + private static function detectAndCleanUtf8(&$data): void { if (is_string($data) && !preg_match('//u', $data)) { $data = preg_replace_callback( '/[\x80-\xFF]+/', - function ($m) { return utf8_encode($m[0]); }, + function ($m) { + return function_exists('mb_convert_encoding') ? mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') : utf8_encode($m[0]); + }, $data ); + if (!is_string($data)) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . self::pcreLastErrorMessage($pcreErrorCode)); + } $data = str_replace( - array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), - array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), + ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], + ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], $data ); } } + + /** + * Converts a string with a valid 'memory_limit' format, to bytes. + * + * @param string|false $val + * @return int|false Returns an integer representing bytes. Returns FALSE in case of error. + */ + public static function expandIniShorthandBytes($val) + { + if (!is_string($val)) { + return false; + } + + // support -1 + if ((int) $val < 0) { + return (int) $val; + } + + if (!preg_match('/^\s*(?\d+)(?:\.\d+)?\s*(?[gmk]?)\s*$/i', $val, $match)) { + return false; + } + + $val = (int) $match['val']; + switch (strtolower($match['unit'])) { + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + + return $val; + } + + /** + * @param array $record + */ + public static function getRecordMessageForException(array $record): string + { + $context = ''; + $extra = ''; + try { + if ($record['context']) { + $context = "\nContext: " . json_encode($record['context']); + } + if ($record['extra']) { + $extra = "\nExtra: " . json_encode($record['extra']); + } + } catch (\Throwable $e) { + // noop + } + + return "\nThe exception occurred while attempting to log: " . $record['message'] . $context . $extra; + } } From f989de03e562ce9387d33e18c8f37be856c0d738 Mon Sep 17 00:00:00 2001 From: asuquoe62-star Date: Tue, 21 Apr 2026 16:13:11 -0400 Subject: [PATCH 2/3] Update htdocs/export/export_word.php Co-authored-by: Mitchell Rysavy --- htdocs/export/export_word.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/export/export_word.php b/htdocs/export/export_word.php index 8ecb73a59..18b3c7e9c 100755 --- a/htdocs/export/export_word.php +++ b/htdocs/export/export_word.php @@ -2,7 +2,7 @@ # # Exports the given HTML content as word document # -include("../includes/db_lib.php"); +require_once("../includes/db_lib.php"); require_once(__DIR__."/word_export_lib.php"); putUILog('export_word', 'X', basename($_SERVER['REQUEST_URI'], ".php"), 'X', 'X', 'X'); From 4e1b93d17e097d163de6812f7e95bf1cd0ff32e9 Mon Sep 17 00:00:00 2001 From: Emmanuel Asuquo Date: Thu, 23 Apr 2026 21:15:31 -0400 Subject: [PATCH 3/3] made some changes to word_export_lib.php --- htdocs/export/word_export_lib.php | 46 +++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/htdocs/export/word_export_lib.php b/htdocs/export/word_export_lib.php index 96b909b0e..3a9a99b19 100644 --- a/htdocs/export/word_export_lib.php +++ b/htdocs/export/word_export_lib.php @@ -31,15 +31,34 @@ function blis_word_send_legacy_doc($html_fragment, $file_prefix) $file_name = blis_word_sanitize_filename_segment($file_prefix).'_'.$date.'.doc'; header('Content-Type: application/vnd.ms-word'); header('Content-Disposition: attachment; filename="'.$file_name.'"'); + $safe_html = blis_word_sanitize_legacy_html($html_fragment); echo "\n"; - echo $html_fragment; + echo $safe_html; echo ""; exit; } +function blis_word_sanitize_legacy_html($html_fragment) +{ + $safe = (string)$html_fragment; + + # Remove high-risk executable elements. + $safe = preg_replace('/<\s*(script|iframe|object|embed|applet|meta|link|style)\b[^>]*>.*?<\s*\/\s*\1\s*>/is', '', $safe); + $safe = preg_replace('/<\s*(script|iframe|object|embed|applet|meta|link|style)\b[^>]*\/?\s*>/is', '', $safe); + + # Remove inline JS event handlers (onclick, onload, etc.). + $safe = preg_replace('/\s+on[a-z]+\s*=\s*(".*?"|\'.*?\'|[^\s>]+)/is', '', $safe); + + # Remove javascript: and data: URL payloads from common attributes. + $safe = preg_replace('/\s+(href|src|xlink:href)\s*=\s*("|\')\s*(javascript:|data:)[^"\']*\2/is', '', $safe); + $safe = preg_replace('/\s+(href|src|xlink:href)\s*=\s*(javascript:|data:)[^\s>]*/is', '', $safe); + + return $safe; +} + function blis_word_export_docx($html_fragment, $file_prefix) { - $pandoc_bin = trim((string)shell_exec('command -v pandoc')); + $pandoc_bin = blis_word_find_pandoc_bin(); if($pandoc_bin === '') { return false; @@ -86,8 +105,31 @@ function blis_word_export_docx($html_fragment, $file_prefix) header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document'); header('Content-Disposition: attachment; filename="'.$file_name.'"'); header('Content-Length: '.filesize($tmp_docx)); + + # Prevent buffered warnings/whitespace from corrupting binary docx output. + while(ob_get_level() > 0) + { + ob_end_clean(); + } + readfile($tmp_docx); @unlink($tmp_docx); exit; } +function blis_word_find_pandoc_bin() +{ + if(PHP_OS_FAMILY === 'Windows') + { + $output = array(); + $exit_code = 0; + exec('where pandoc', $output, $exit_code); + if($exit_code === 0 && isset($output[0])) + { + return trim($output[0]); + } + return ''; + } + + return trim((string)shell_exec('command -v pandoc')); +}