Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MessageFormatterHandler added. Removed deprecations from StringHandler. #43

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/Handler/MessageFormatterHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace GuzzleLogMiddleware\Handler;

use GuzzleHttp\MessageFormatterInterface;
use GuzzleHttp\TransferStats;
use GuzzleLogMiddleware\Handler\LogLevelStrategy\LogLevelStrategyInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Throwable;

/**
* Delegates responsibility of log message formatting to MessageFormatterInterface.
*/
class MessageFormatterHandler extends AbstractHandler
{
/**
* @var MessageFormatterInterface
*/
private $messageFormatter;

public function __construct(
MessageFormatterInterface $messageFormatter,
LogLevelStrategyInterface $logLevelStrategy = null
) {
$this->messageFormatter = $messageFormatter;
$this->logLevelStrategy = $logLevelStrategy === null ? $this->getDefaultStrategy() : $logLevelStrategy;
}

public function log(LoggerInterface $logger, RequestInterface $request, ?ResponseInterface $response = null, ?Throwable $exception = null, ?TransferStats $stats = null, array $options = []): void
{
if (
$request->getBody()->isSeekable() === false
|| $request->getBody()->isReadable() === false
|| (
$response !== null && (
$response->getBody()->isSeekable() === false
|| $response->getBody()->isReadable() === false
)
)
) {
$logger->warning('StringHandler can not log request/response because the body is not seekable/readable.');
return;
}

$message = $this->messageFormatter->format($request, $response, $exception);
$reason = $exception ?? $response ?? $request;
$level = $this->logLevelStrategy->getLevel($reason, $options);
$logger->log($level, $message);
}
}
5 changes: 3 additions & 2 deletions src/Handler/StringHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace GuzzleLogMiddleware\Handler;

use GuzzleHttp\Psr7\Message;
use GuzzleHttp\TransferStats;
use GuzzleLogMiddleware\Handler\LogLevelStrategy\LogLevelStrategyInterface;
use Psr\Http\Message\RequestInterface;
Expand Down Expand Up @@ -53,7 +54,7 @@ private function logRequest(LoggerInterface $logger, RequestInterface $value, ar
return;
}

$str = \GuzzleHttp\Psr7\str($value);
$str = Message::toString($value);

$level = $this->logLevelStrategy->getLevel($value, $options);
$logger->log($level, 'Guzzle HTTP request:' . "\n" . $str);
Expand All @@ -67,7 +68,7 @@ private function logResponse(LoggerInterface $logger, ResponseInterface $value,
return;
}

$str = \GuzzleHttp\Psr7\str($value);
$str = Message::toString($value);

$level = $this->logLevelStrategy->getLevel($value, $options);
$logger->log($level, 'Guzzle HTTP response:' . "\n" . $str);
Expand Down
178 changes: 178 additions & 0 deletions tests/Handler/MessageFormatterHandlerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<?php

declare(strict_types=1);

namespace GuzzleLogMiddleware\Test\Handler;

use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\MessageFormatterInterface;
use GuzzleLogMiddleware\Handler\LogLevelStrategy\LogLevelStrategyInterface;
use GuzzleLogMiddleware\Handler\MessageFormatterHandler;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Log\LoggerInterface;

/**
* @covers \GuzzleLogMiddleware\Handler\MessageFormatterHandler
*/
class MessageFormatterHandlerTest extends TestCase
{
/**
* @covers \GuzzleLogMiddleware\Handler\MessageFormatterHandler::log()
* @dataProvider successLogDataProvider
*/
public function testCanLog(
RequestInterface $request,
?ResponseInterface $response,
?\Throwable $expectedException,
?string $expectedLogMessage
): void {
$formatter = $this->createMock(MessageFormatterInterface::class);
$strategy = $this->createMock(LogLevelStrategyInterface::class);
$logger = $this->createMock(LoggerInterface::class);
$loglevel = 'LOG_LEVEL';
$formatter
->expects($this->once())
->method('format')
->with(
$this->equalTo($request),
$this->equalTo($response),
$this->equalTo($expectedException)
)
->willReturn($expectedLogMessage);
$strategy
->expects($this->once())
->method('getLevel')
->with(
$this->equalTo($expectedException ?? $response ?? $request)
)
->willReturn($loglevel);
$logger
->expects($this->once())
->method('log')
->with(
$this->equalTo($loglevel),
$this->equalTo($expectedLogMessage)
);

$handler = new MessageFormatterHandler($formatter, $strategy);
$handler->log($logger, $request, $response, $expectedException);
}

public function successLogDataProvider(): \Generator
{
yield 'Response is not given.' => [
// GIVEN request
$this->mockRequest(true, true),
// GIVEN response
null,
// GIVEN exception
null,
// EXPECTED logger message
'Expected log message.'
];

yield 'Response and Response are seekable.' => [
// GIVEN request
$this->mockRequest(true, true),
// GIVEN response
$this->mockResponse(true, true),
// GIVEN exception
null,
// EXPECTED logger message
'Expected log message.'
];

yield 'Response and Response are seekable. Exception given.' => [
// GIVEN request
$this->mockRequest(true, true),
// GIVEN response
$this->mockResponse(true, true),
// GIVEN exception
new TransferException(),
// EXPECTED logger message
'Expected log message.'
];
}

/**
* @covers \GuzzleLogMiddleware\Handler\MessageFormatterHandler::log()
* @dataProvider cannotLogDataProvider
*/
public function testCannotLog(
RequestInterface $request,
?ResponseInterface $response
): void {
$formatter = $this->createMock(MessageFormatterInterface::class);
$strategy = $this->createMock(LogLevelStrategyInterface::class);
$logger = $this->createMock(LoggerInterface::class);

$formatter->expects($this->never())->method('format');
$strategy->expects($this->never())->method('getLevel');
$logger->expects($this->once())
->method('warning')
->with(
$this->equalTo('StringHandler can not log request/response because the body is not seekable/readable.')
);

$handler = new MessageFormatterHandler($formatter, $strategy);
$handler->log($logger, $request, $response);
}

public function cannotLogDataProvider(): \Generator
{
yield 'Request is not seekable.' => [
$this->mockRequest(false, true),
$this->mockResponse(true, true),
];

yield 'Request is not readable.' => [
$this->mockRequest(true, false),
$this->mockResponse(true, true),
];

yield 'Response is not seekable.' => [
$this->mockRequest(true, true),
$this->mockResponse(false, true),
];

yield 'Response is not readable.' => [
$this->mockRequest(true, true),
$this->mockResponse(true, false),
];
}

/**
* @return MockObject|RequestInterface
*/
private function mockRequest(bool $isSeekable, bool $isReadable)
{
$mock = $this->createMock(RequestInterface::class);
$mock->method('getBody')->willReturn($this->mockStream($isSeekable, $isReadable));
return $mock;
}

/**
* @return MockObject|ResponseInterface
*/
private function mockResponse(bool $isSeekable, bool $isReadable)
{
$mock = $this->createMock(ResponseInterface::class);
$mock->method('getBody')->willReturn($this->mockStream($isSeekable, $isReadable));
return $mock;
}

/**
* @return mixed|MockObject|StreamInterface
*/
private function mockStream(bool $isSeekable, bool $isReadable)
{
$mock = $this->createMock(StreamInterface::class);
$mock->method('isSeekable')->willReturn($isSeekable);
$mock->method('isReadable')->willReturn($isReadable);
return $mock;
}
}