From 57e3fe3173e83bceecf9b25fac31d60ec57b15f3 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 14 Jul 2021 23:25:49 +0200 Subject: [PATCH 1/2] Allow psr/log 2.0 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 7ed2e3d3..5ac50eec 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ ], "require": { "php": ">=7.2", - "psr/log": "^1.0.1" + "psr/log": "^1.0.1 || ^2.0" }, "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", @@ -53,7 +53,7 @@ "psr-4": {"Monolog\\": "tests/Monolog"} }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "1.0.0 || 2.0.0" }, "extra": { "branch-alias": { From 203bb1b6f567f1fc266e381c6e955326afddf5c3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 13 Sep 2021 12:13:06 +0200 Subject: [PATCH 2/2] Fix CI to allow psr/log v2 --- .github/workflows/continuous-integration.yml | 16 ++- composer.json | 3 +- phpstan.neon.dist | 3 + tests/Monolog/Handler/RollbarHandlerTest.php | 2 + tests/Monolog/PsrLogCompatTest.php | 130 ++++++++++++++++++- 5 files changed, 144 insertions(+), 10 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 9852d780..19806498 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -58,18 +58,24 @@ jobs: - name: Add require for mongodb/mongodb to make tests runnable run: 'composer require ${{ env.COMPOSER_FLAGS }} mongodb/mongodb --dev --no-update' - # This does not affect runtime, only tests were fixed in psr/log 1.1.2 so it's - # ok to require this only when running tests - - name: Bump required version of psr/log for tests purposes to fix the --prefer-lowest builds - run: 'composer require ${{ env.COMPOSER_FLAGS }} psr/log:^1.1.2 --no-update' - - name: "Handle lowest dependencies update" if: "contains(matrix.dependencies, 'lowest')" run: "echo \"COMPOSER_FLAGS=$COMPOSER_FLAGS --prefer-lowest\" >> $GITHUB_ENV" + - name: "Ensure psr/log v2 is installed" + if: "contains(matrix.dependencies, 'highest') && matrix.php-version >= '8.0'" + run: composer require -W psr/log:^2 + - name: "Install latest dependencies" run: | composer update ${{ env.COMPOSER_FLAGS }} - name: "Run tests" run: "composer exec phpunit -- --verbose" + + - name: "Run tests with rollbar" + run: | + composer require psr/log:'^1.1|^2' --no-update + composer require rollbar/rollbar:^1.3 --no-update + composer update -W ${{ env.COMPOSER_FLAGS }} + composer exec phpunit -- --verbose --filter Rollbar diff --git a/composer.json b/composer.json index 5ac50eec..99e791a8 100644 --- a/composer.json +++ b/composer.json @@ -27,8 +27,7 @@ "phpspec/prophecy": "^1.6.1", "phpunit/phpunit": "^8.5", "predis/predis": "^1.1", - "rollbar/rollbar": "^1.3", - "ruflin/elastica": ">=0.90 <7.0.1", + "ruflin/elastica": ">=0.90@dev", "swiftmailer/swiftmailer": "^5.3|^6.0", "phpstan/phpstan": "^0.12.91" }, diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 961f7ce4..275089a0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -35,3 +35,6 @@ parameters: - '#::popProcessor\(\) should return callable#' - '#Parameter \#1 \$ of callable \(callable\(Monolog\\Handler\\Record\): Monolog\\Handler\\Record\)#' - '#is incompatible with native type array.#' + + # can be removed when https://github.com/rollbar/rollbar-php/pull/536 will be merged + - '#Rollbar\\RollbarLogger#' diff --git a/tests/Monolog/Handler/RollbarHandlerTest.php b/tests/Monolog/Handler/RollbarHandlerTest.php index 40442105..0aa7cbff 100644 --- a/tests/Monolog/Handler/RollbarHandlerTest.php +++ b/tests/Monolog/Handler/RollbarHandlerTest.php @@ -22,6 +22,8 @@ use Rollbar\RollbarLogger; * @see https://rollbar.com/docs/notifier/rollbar-php/ * * @coversDefaultClass Monolog\Handler\RollbarHandler + * + * @requires function \Rollbar\RollbarLogger::__construct */ class RollbarHandlerTest extends TestCase { diff --git a/tests/Monolog/PsrLogCompatTest.php b/tests/Monolog/PsrLogCompatTest.php index a4bbcd0c..eaeb3552 100644 --- a/tests/Monolog/PsrLogCompatTest.php +++ b/tests/Monolog/PsrLogCompatTest.php @@ -11,16 +11,21 @@ namespace Monolog; +use DateTimeZone; use Monolog\Handler\TestHandler; use Monolog\Formatter\LineFormatter; use Monolog\Processor\PsrLogMessageProcessor; +use PHPUnit\Framework\TestCase; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; use Psr\Log\Test\LoggerInterfaceTest; -class PsrLogCompatTest extends LoggerInterfaceTest +class PsrLogCompatTest extends TestCase { private $handler; - public function getLogger() + public function getLogger(): LoggerInterface { $logger = new Logger('foo'); $logger->pushHandler($handler = new TestHandler); @@ -32,7 +37,7 @@ class PsrLogCompatTest extends LoggerInterfaceTest return $logger; } - public function getLogs() + public function getLogs(): array { $convert = function ($record) { $lower = function ($match) { @@ -44,4 +49,123 @@ class PsrLogCompatTest extends LoggerInterfaceTest return array_map($convert, $this->handler->getRecords()); } + + public function testImplements() + { + $this->assertInstanceOf(LoggerInterface::class, $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + "$level message of level $level with context: Bob", + "$level message of level $level with context: Bob", + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + + $this->expectException(InvalidArgumentException::class); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + $string = uniqid('DUMMY'); + $dummy = $this->createStringable($string); + $dummy->expects($this->once()) + ->method('__toString'); + + $this->getLogger()->warning($dummy); + + $expected = array("warning $string"); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => $this->createStringable()), + 'object' => new \DateTime('now', new DateTimeZone('Europe/London')), + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } + + /** + * Creates a mock of a `Stringable`. + * + * @param string $string The string that must be represented by the stringable. + * @return \PHPUnit_Framework_MockObject_MockObject A mock of an object that has a `__toString()` method. + */ + protected function createStringable($string = '') + { + $mock = $this->getMockBuilder('Stringable') + ->setMethods(array('__toString')) + ->getMock(); + + $mock->method('__toString') + ->will($this->returnValue($string)); + + return $mock; + } }