diff --git a/.gitattributes b/.gitattributes index bf5ad497..a1e258de 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,7 @@ /doc export-ignore /tests export-ignore /.* export-ignore -/phpstan.neon.dist export-ignore +/phpstan* export-ignore /phpunit.xml.dist export-ignore /_config.yml export-ignore /UPGRADE.md export-ignore diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md index 71ce0c2b..df38260f 100644 --- a/.github/ISSUE_TEMPLATE/Bug_Report.md +++ b/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -4,6 +4,6 @@ about: Create a bug report labels: Bug --- -Monolog version 1|2 +Monolog version 1|2|3? Write your bug report here. diff --git a/.github/ISSUE_TEMPLATE/Question.md b/.github/ISSUE_TEMPLATE/Question.md index b2810fa3..f62632c4 100644 --- a/.github/ISSUE_TEMPLATE/Question.md +++ b/.github/ISSUE_TEMPLATE/Question.md @@ -4,6 +4,6 @@ about: Ask a question regarding software usage labels: Support --- -Monolog version 1|2 +Monolog version 1|2|3? Write your question here. diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index bab50dba..59781f7a 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -6,7 +6,7 @@ on: jobs: tests: - name: "CI" + name: "CI (PHP ${{ matrix.php-version }}, ${{ matrix.dependencies }} deps)" runs-on: "${{ matrix.operating-system }}" @@ -19,6 +19,8 @@ jobs: dependencies: [highest] + composer-options: [""] + operating-system: - "ubuntu-latest" @@ -26,6 +28,10 @@ jobs: - php-version: "8.1" dependencies: lowest operating-system: ubuntu-latest + - php-version: "8.2" + dependencies: highest + operating-system: ubuntu-latest + composer-options: "--ignore-platform-req=php+" steps: - name: "Checkout" @@ -61,9 +67,10 @@ jobs: composer config --no-plugins allow-plugins.ocramius/package-versions true - name: "Update dependencies with composer" - uses: "ramsey/composer-install@v1" + uses: "ramsey/composer-install@v2" with: dependency-versions: "${{ matrix.dependencies }}" + composer-options: "${{ matrix.composer-options }}" - name: "Run tests" run: "composer exec phpunit -- --exclude-group Elasticsearch,Elastica --verbose" @@ -73,7 +80,7 @@ jobs: run: | composer remove --no-update --dev graylog2/gelf-php ruflin/elastica elasticsearch/elasticsearch rollbar/rollbar composer require --no-update psr/log:^3 - composer update -W + composer update ${{ matrix.composer-options }} composer exec phpunit -- --exclude-group Elasticsearch,Elastica --verbose tests-es-7: @@ -131,8 +138,12 @@ jobs: - name: "Change dependencies" run: "composer require --no-update --no-interaction --dev elasticsearch/elasticsearch:^${{ matrix.es-version }}" + - name: "Allow composer plugin to run" + if: "matrix.php-version == '7.4' && matrix.dependencies == 'lowest'" + run: "composer config allow-plugins.ocramius/package-versions true" + - name: "Update dependencies with composer" - uses: "ramsey/composer-install@v1" + uses: "ramsey/composer-install@v2" with: dependency-versions: "${{ matrix.dependencies }}" @@ -205,8 +216,12 @@ jobs: composer remove --no-update --dev graylog2/gelf-php ruflin/elastica elasticsearch/elasticsearch rollbar/rollbar composer require --no-update --no-interaction --dev elasticsearch/elasticsearch:^8 + - name: "Allow composer plugin to run" + if: "matrix.php-version == '7.4' && matrix.dependencies == 'lowest'" + run: "composer config allow-plugins.ocramius/package-versions true" + - name: "Update dependencies with composer" - uses: "ramsey/composer-install@v1" + uses: "ramsey/composer-install@v2" with: dependency-versions: "${{ matrix.dependencies }}" diff --git a/composer.json b/composer.json index d050a2c8..3a48e6db 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,6 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "php-console/php-console": "^3.1.3", "phpstan/phpstan": "^1.4", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", @@ -47,7 +46,6 @@ "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "php-console/php-console": "Allow sending log messages to Google Chrome", "ext-mbstring": "Allow to work properly with unicode symbols", "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", diff --git a/doc/02-handlers-formatters-processors.md b/doc/02-handlers-formatters-processors.md index 72c5da01..d16e674a 100644 --- a/doc/02-handlers-formatters-processors.md +++ b/doc/02-handlers-formatters-processors.md @@ -51,7 +51,6 @@ [php-amqplib](https://github.com/php-amqplib/php-amqplib) library. - [_GelfHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/GelfHandler.php): Logs records to a [Graylog2](http://www.graylog2.org) server. Requires package [graylog2/gelf-php](https://github.com/bzikarsky/gelf-php). -- [_CubeHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/CubeHandler.php): Logs records to a [Cube](http://square.github.com/cube/) server. - [_ZendMonitorHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/ZendMonitorHandler.php): Logs records to the Zend Monitor present in Zend Server. - [_NewRelicHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/NewRelicHandler.php): Logs records to a [NewRelic](http://newrelic.com/) application. - [_LogglyHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/LogglyHandler.php): Logs records to a [Loggly](http://www.loggly.com/) account. @@ -72,8 +71,6 @@ inline `console` messages within Chrome. - [_BrowserConsoleHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/BrowserConsoleHandler.php): Handler to send logs to browser's Javascript `console` with no browser extension required. Most browsers supporting `console` API are supported. -- [_PHPConsoleHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/PHPConsoleHandler.php): Handler for [PHP Console](https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef), providing - inline `console` and notification popup messages within Chrome. ### Log to databases diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ac582e23..9661ca20 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5,6 +5,11 @@ parameters: count: 1 path: src/Monolog/ErrorHandler.php + - + message: "#^Return type \\(array\\\\|bool\\|float\\|int\\|object\\|string\\|null\\) of method Monolog\\\\Formatter\\\\JsonFormatter\\:\\:normalize\\(\\) should be covariant with return type \\(array\\\\|bool\\|float\\|int\\|string\\|null\\) of method Monolog\\\\Formatter\\\\NormalizerFormatter\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Monolog/Formatter/JsonFormatter.php + - message: "#^Cannot access offset 'table' on array\\\\|bool\\|float\\|int\\|object\\|string\\.$#" count: 1 @@ -70,16 +75,16 @@ parameters: count: 1 path: src/Monolog/Handler/MandrillHandler.php - - - message: "#^Method Monolog\\\\Handler\\\\PHPConsoleHandler\\:\\:initOptions\\(\\) should return array\\{enabled\\: bool, classesPartialsTraceIgnore\\: array\\, debugTagsKeysInContext\\: array\\, useOwnErrorsHandler\\: bool, useOwnExceptionsHandler\\: bool, sourcesBasePath\\: string\\|null, registerHelper\\: bool, serverEncoding\\: string\\|null, \\.\\.\\.\\} but returns non\\-empty\\-array\\<'classesPartialsTrac…'\\|'dataStorage'\\|'debugTagsKeysInCont…'\\|'detectDumpTraceAndS…'\\|'dumperDetectCallbac…'\\|'dumperDumpSizeLimit'\\|'dumperItemsCountLim…'\\|'dumperItemSizeLimit'\\|'dumperLevelLimit'\\|'enabled'\\|'enableEvalListener'\\|'enableSslOnlyMode'\\|'headersLimit'\\|'ipMasks'\\|'password'\\|'registerHelper'\\|'serverEncoding'\\|'sourcesBasePath'\\|'useOwnErrorsHandler'\\|'useOwnExceptionsHan…', array\\\\|bool\\|int\\|PhpConsole\\\\Storage\\|string\\|null\\>\\.$#" - count: 1 - path: src/Monolog/Handler/PHPConsoleHandler.php - - message: "#^Instanceof between Monolog\\\\Handler\\\\HandlerInterface and Monolog\\\\Handler\\\\HandlerInterface will always evaluate to true\\.$#" count: 1 path: src/Monolog/Handler/SamplingHandler.php + - + message: "#^Match expression does not handle remaining value\\: 'EMERGENCY'$#" + count: 1 + path: src/Monolog/Level.php + - message: "#^Variable property access on \\$this\\(Monolog\\\\LogRecord\\)\\.$#" count: 4 @@ -110,3 +115,8 @@ parameters: count: 1 path: src/Monolog/Processor/UidProcessor.php + - + message: "#^Parameter \\#2 \\$callback of function preg_replace_callback expects callable\\(array\\\\)\\: string, Closure\\(mixed\\)\\: array\\\\|string\\|false given\\.$#" + count: 1 + path: src/Monolog/Utils.php + diff --git a/phpstan.neon.dist b/phpstan.neon.dist index fa2f6fd9..580b169c 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -8,6 +8,9 @@ parameters: - src/ # - tests/ + excludePaths: + - 'src/Monolog/Handler/PHPConsoleHandler.php' + ignoreErrors: - '#zend_monitor_|ZEND_MONITOR_#' - '#MongoDB\\(Client|Collection)#' diff --git a/src/Monolog/Formatter/JsonFormatter.php b/src/Monolog/Formatter/JsonFormatter.php index e77427db..b2fc5bcb 100644 --- a/src/Monolog/Formatter/JsonFormatter.php +++ b/src/Monolog/Formatter/JsonFormatter.php @@ -11,6 +11,7 @@ namespace Monolog\Formatter; +use Stringable; use Throwable; use Monolog\LogRecord; @@ -139,6 +140,8 @@ class JsonFormatter extends NormalizerFormatter /** * Normalizes given $data. + * + * @return null|scalar|array|object */ protected function normalize(mixed $data, int $depth = 0): mixed { @@ -162,12 +165,25 @@ class JsonFormatter extends NormalizerFormatter return $normalized; } - if ($data instanceof \DateTimeInterface) { - return $this->formatDate($data); - } + if (is_object($data)) { + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } - if ($data instanceof Throwable) { - return $this->normalizeException($data, $depth); + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); + } + + // if the object has specific json serializability we want to make sure we skip the __toString treatment below + if ($data instanceof \JsonSerializable) { + return $data; + } + + if ($data instanceof Stringable) { + return $data->__toString(); + } + + return $data; } if (is_resource($data)) { diff --git a/src/Monolog/Formatter/LineFormatter.php b/src/Monolog/Formatter/LineFormatter.php index cdfcca61..3f4a4106 100644 --- a/src/Monolog/Formatter/LineFormatter.php +++ b/src/Monolog/Formatter/LineFormatter.php @@ -149,6 +149,12 @@ class LineFormatter extends NormalizerFormatter if (($previous = $e->getPrevious()) instanceof \Throwable) { do { + $depth++; + if ($depth > $this->maxNormalizeDepth) { + $str .= '\n[previous exception] Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; + break; + } + $str .= "\n[previous exception] " . $this->formatException($previous); } while ($previous = $previous->getPrevious()); } diff --git a/src/Monolog/Formatter/NormalizerFormatter.php b/src/Monolog/Formatter/NormalizerFormatter.php index 98675038..1323587b 100644 --- a/src/Monolog/Formatter/NormalizerFormatter.php +++ b/src/Monolog/Formatter/NormalizerFormatter.php @@ -218,6 +218,10 @@ class NormalizerFormatter implements FormatterInterface */ protected function normalizeException(Throwable $e, int $depth = 0) { + if ($depth > $this->maxNormalizeDepth) { + return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization']; + } + if ($e instanceof \JsonSerializable) { return (array) $e->jsonSerialize(); } diff --git a/src/Monolog/Handler/AmqpHandler.php b/src/Monolog/Handler/AmqpHandler.php index 5e361b83..b25e4f13 100644 --- a/src/Monolog/Handler/AmqpHandler.php +++ b/src/Monolog/Handler/AmqpHandler.php @@ -23,6 +23,9 @@ class AmqpHandler extends AbstractProcessingHandler { protected AMQPExchange|AMQPChannel $exchange; + /** @var array */ + private array $extraAttributes = []; + protected string $exchangeName; /** @@ -41,6 +44,29 @@ class AmqpHandler extends AbstractProcessingHandler parent::__construct($level, $bubble); } + /** + * @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 $this + */ + public function setExtraAttributes(array $extraAttributes): self + { + $this->extraAttributes = $extraAttributes; + return $this; + } + /** * @inheritDoc */ @@ -50,14 +76,18 @@ class AmqpHandler extends AbstractProcessingHandler $routingKey = $this->getRoutingKey($record); if ($this->exchange instanceof AMQPExchange) { + $attributes = [ + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ]; + if (\count($this->extraAttributes) > 0) { + $attributes = array_merge($attributes, $this->extraAttributes); + } $this->exchange->publish( $data, $routingKey, 0, - [ - 'delivery_mode' => 2, - 'content_type' => 'application/json', - ] + $attributes ); } else { $this->exchange->basic_publish( diff --git a/src/Monolog/Handler/ChromePHPHandler.php b/src/Monolog/Handler/ChromePHPHandler.php index e2b63498..3742d47d 100644 --- a/src/Monolog/Handler/ChromePHPHandler.php +++ b/src/Monolog/Handler/ChromePHPHandler.php @@ -145,7 +145,7 @@ class ChromePHPHandler extends AbstractProcessingHandler } $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); - $data = base64_encode(utf8_encode($json)); + $data = base64_encode($json); if (strlen($data) > 3 * 1024) { self::$overflowed = true; @@ -156,8 +156,8 @@ class ChromePHPHandler extends AbstractProcessingHandler datetime: new DateTimeImmutable(true), ); 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) !== '') { diff --git a/src/Monolog/Handler/CubeHandler.php b/src/Monolog/Handler/CubeHandler.php index 28294d68..8388f5ad 100644 --- a/src/Monolog/Handler/CubeHandler.php +++ b/src/Monolog/Handler/CubeHandler.php @@ -18,8 +18,9 @@ use Monolog\LogRecord; /** * Logs to Cube. * - * @link http://square.github.com/cube/ + * @link https://github.com/square/cube/wiki * @author Wan Chen + * @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 { diff --git a/src/Monolog/Handler/PHPConsoleHandler.php b/src/Monolog/Handler/PHPConsoleHandler.php index a53d26e2..8aa78e4c 100644 --- a/src/Monolog/Handler/PHPConsoleHandler.php +++ b/src/Monolog/Handler/PHPConsoleHandler.php @@ -27,7 +27,7 @@ use PhpConsole\Storage; * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely * * Usage: - * 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef + * 1. Install Google Chrome extension [now dead and removed from the chrome store] * 2. See overview https://github.com/barbushin/php-console#overview * 3. Install PHP Console library https://github.com/barbushin/php-console#installation * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) @@ -83,6 +83,8 @@ use PhpConsole\Storage; * detectDumpTraceAndSource?: bool, * dataStorage?: Storage|null * } + * + * @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 { diff --git a/src/Monolog/Handler/RedisPubSubHandler.php b/src/Monolog/Handler/RedisPubSubHandler.php index 96012828..fa8e9e9f 100644 --- a/src/Monolog/Handler/RedisPubSubHandler.php +++ b/src/Monolog/Handler/RedisPubSubHandler.php @@ -32,7 +32,7 @@ use Redis; class RedisPubSubHandler extends AbstractProcessingHandler { /** @var Predis|Redis */ - private $redisClient; + private Predis|Redis $redisClient; private string $channelKey; /** diff --git a/src/Monolog/Handler/StreamHandler.php b/src/Monolog/Handler/StreamHandler.php index 0bc18ef1..027a7217 100644 --- a/src/Monolog/Handler/StreamHandler.php +++ b/src/Monolog/Handler/StreamHandler.php @@ -193,7 +193,7 @@ class StreamHandler extends AbstractProcessingHandler set_error_handler([$this, 'customErrorHandler']); $status = mkdir($dir, 0777, true); restore_error_handler(); - if (false === $status && !is_dir($dir)) { + if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage, 'File exists') === false) { throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage, $dir)); } } diff --git a/src/Monolog/Utils.php b/src/Monolog/Utils.php index a9cf0f76..9dae2535 100644 --- a/src/Monolog/Utils.php +++ b/src/Monolog/Utils.php @@ -200,7 +200,7 @@ final class Utils $data = preg_replace_callback( '/[\x80-\xFF]+/', function ($m) { - return utf8_encode($m[0]); + return function_exists('mb_convert_encoding') ? mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') : utf8_encode($m[0]); }, $data ); diff --git a/tests/Monolog/Formatter/JsonFormatterTest.php b/tests/Monolog/Formatter/JsonFormatterTest.php index 7a172dd2..42936e1f 100644 --- a/tests/Monolog/Formatter/JsonFormatterTest.php +++ b/tests/Monolog/Formatter/JsonFormatterTest.php @@ -13,6 +13,7 @@ namespace Monolog\Formatter; use Monolog\Level; use Monolog\LogRecord; +use JsonSerializable; use Monolog\Test\TestCase; class JsonFormatterTest extends TestCase @@ -289,4 +290,58 @@ class JsonFormatterTest extends TestCase $record ); } + + public function testFormatObjects() + { + $formatter = new JsonFormatter(); + + $record = $formatter->format($this->getRecord( + Level::Debug, + 'Testing', + channel: 'test', + datetime: new \DateTimeImmutable('2022-02-22 00:00:00'), + context: [ + 'public' => new TestJsonNormPublic, + 'private' => new TestJsonNormPrivate, + 'withToStringAndJson' => new TestJsonNormWithToStringAndJson, + 'withToString' => new TestJsonNormWithToString, + ], + )); + + $this->assertSame( + '{"message":"Testing","context":{"public":{"foo":"fooValue"},"private":{},"withToStringAndJson":["json serialized"],"withToString":"stringified"},"level":100,"level_name":"DEBUG","channel":"test","datetime":"2022-02-22T00:00:00+00:00","extra":{}}'."\n", + $record + ); + } +} + +class TestJsonNormPublic +{ + public $foo = 'fooValue'; +} + +class TestJsonNormPrivate +{ + private $foo = 'fooValue'; +} + +class TestJsonNormWithToStringAndJson implements JsonSerializable +{ + public function jsonSerialize() + { + return ['json serialized']; + } + + public function __toString() + { + return 'SHOULD NOT SHOW UP'; + } +} + +class TestJsonNormWithToString +{ + public function __toString() + { + return 'stringified'; + } } diff --git a/tests/Monolog/Formatter/NormalizerFormatterTest.php b/tests/Monolog/Formatter/NormalizerFormatterTest.php index 2b99d438..f6668027 100644 --- a/tests/Monolog/Formatter/NormalizerFormatterTest.php +++ b/tests/Monolog/Formatter/NormalizerFormatterTest.php @@ -364,8 +364,10 @@ class NormalizerFormatterTest extends TestCase $record = $this->getRecord(context: ['exception' => $e]); $result = $formatter->format($record); + // See https://github.com/php/php-src/issues/8810 fixed in PHP 8.2 + $offset = PHP_VERSION_ID >= 80200 ? 13 : 11; $this->assertSame( - __FILE__.':'.(__LINE__-9), + __FILE__.':'.(__LINE__ - $offset), $result['context']['exception']['trace'][0] ); } diff --git a/tests/Monolog/Handler/ChromePHPHandlerTest.php b/tests/Monolog/Handler/ChromePHPHandlerTest.php index 2dcb5200..4b2f2953 100644 --- a/tests/Monolog/Handler/ChromePHPHandlerTest.php +++ b/tests/Monolog/Handler/ChromePHPHandlerTest.php @@ -38,7 +38,7 @@ class ChromePHPHandlerTest extends TestCase $handler->handle($this->getRecord(Level::Warning)); $expected = [ - 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode([ + 'X-ChromeLogger-Data' => base64_encode(json_encode([ 'version' => '4.0', 'columns' => ['label', 'log', 'backtrace', 'type'], 'rows' => [ @@ -46,7 +46,7 @@ class ChromePHPHandlerTest extends TestCase 'test', ], 'request_uri' => '', - ]))), + ])), ]; $this->assertEquals($expected, $handler->getHeaders()); @@ -72,7 +72,7 @@ class ChromePHPHandlerTest extends TestCase $handler->handle($this->getRecord(Level::Warning, str_repeat('b', 2 * 1024))); $expected = [ - 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode([ + 'X-ChromeLogger-Data' => base64_encode(json_encode([ 'version' => '4.0', 'columns' => ['label', 'log', 'backtrace', 'type'], 'rows' => [ @@ -96,7 +96,7 @@ class ChromePHPHandlerTest extends TestCase ], ], 'request_uri' => '', - ]))), + ])), ]; $this->assertEquals($expected, $handler->getHeaders()); @@ -115,7 +115,7 @@ class ChromePHPHandlerTest extends TestCase $handler2->handle($this->getRecord(Level::Warning)); $expected = [ - 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode([ + 'X-ChromeLogger-Data' => base64_encode(json_encode([ 'version' => '4.0', 'columns' => ['label', 'log', 'backtrace', 'type'], 'rows' => [ @@ -125,7 +125,7 @@ class ChromePHPHandlerTest extends TestCase 'test', ], 'request_uri' => '', - ]))), + ])), ]; $this->assertEquals($expected, $handler2->getHeaders());