diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5f3e9d3..446c622 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -72,7 +72,7 @@ jobs: - name: "Install PHP" uses: "shivammathur/setup-php@v2" with: - php-version: 7.4" + php-version: "7.4" ini-values: "memory_limit=-1" - name: "Install dependencies (Composer)" @@ -81,3 +81,26 @@ jobs: - name: "Run unit tests (PHPUnit)" shell: "bash" run: "composer test" + + unit-tests-psr-log-v3: + name: "Unit tests (psr/log v3)" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout repository" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "8.1" + ini-values: "memory_limit=-1" + + - name: "Install dependencies with psr/log v3" + shell: "bash" + run: | + composer config platform.php 8.1 + composer update --with psr/log:^3 --with-all-dependencies --no-interaction + + - name: "Run unit tests (PHPUnit)" + shell: "bash" + run: "composer test" diff --git a/composer.json b/composer.json index f5555f8..cad883d 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "psr/http-message": "^1.0", "psr/http-server-handler": "^1.0", "psr/http-server-middleware": "^1.0", - "psr/log": "^1.1" + "psr/log": "^1.1 || ^2.0 || ^3.0" }, "autoload": { "psr-4": { diff --git a/src/StderrLogger.php b/src/StderrLogger.php index 1436fd2..430df56 100644 --- a/src/StderrLogger.php +++ b/src/StderrLogger.php @@ -46,8 +46,16 @@ public function __construct(string $minLevel = LogLevel::WARNING, $stream = 'php } } - public function log($level, $message, array $context = []) + public function log($level, $message, array $context = []): void { + if (!is_string($level)) { + if (is_object($level) && method_exists($level, '__toString')) { + $level = (string) $level; + } else { + throw new InvalidArgumentException('Invalid log level: must be a string or stringable object'); + } + } + if (!isset(self::LOG_LEVEL_MAP[$level])) { throw new InvalidArgumentException("Invalid log level: {$level}"); } @@ -67,6 +75,14 @@ public function log($level, $message, array $context = []) $context['exception'] = explode("\n", (string) $exception); } + if (!is_string($message)) { + if (is_object($message) && method_exists($message, '__toString')) { + $message = (string) $message; + } else { + throw new InvalidArgumentException('Invalid log message: must be a string or stringable object'); + } + } + fwrite($this->stream, json_encode(compact('level', 'message', 'context')) . "\n"); } } diff --git a/tests/Integration/IntegTestCase.php b/tests/Integration/IntegTestCase.php index 3631743..8c84e74 100644 --- a/tests/Integration/IntegTestCase.php +++ b/tests/Integration/IntegTestCase.php @@ -102,7 +102,7 @@ protected function createHttpServer(ServerRequestInterface $request): HttpServer protected function failOnLoggedErrors(): void { - $this->logger->method('error')->willReturnCallback(function (string $message, array $context) { + $this->logger->method('error')->willReturnCallback(function ($message, array $context = []) { $message = "Logged an error: {$message}\nContext:\n"; foreach ($context as $key => $value) { $message .= "- {$key}: {$value}\n"; diff --git a/tests/Integration/StderrLoggerTest.php b/tests/Integration/StderrLoggerTest.php new file mode 100644 index 0000000..86724bb --- /dev/null +++ b/tests/Integration/StderrLoggerTest.php @@ -0,0 +1,52 @@ +log(LogLevel::ERROR, $message, ['foo' => 'bar']); + + rewind($stream); + $line = trim((string) stream_get_contents($stream)); + $payload = json_decode($line, true); + + $this->assertSame('stringable-message', $payload['message']); + $this->assertSame(LogLevel::ERROR, $payload['level']); + $this->assertSame(['foo' => 'bar'], $payload['context']); + } + + public function testNonStringLevelThrowsInvalidArgumentException(): void + { + $stream = fopen('php://temp', 'a+'); + $logger = new StderrLogger(LogLevel::DEBUG, $stream); + + $this->expectException(InvalidArgumentException::class); + $logger->log([], 'hello'); + } + + public function testNonStringMessageThrowsInvalidArgumentException(): void + { + $stream = fopen('php://temp', 'a+'); + $logger = new StderrLogger(LogLevel::DEBUG, $stream); + + $this->expectException(InvalidArgumentException::class); + $logger->log(LogLevel::ERROR, []); + } +}