-
Notifications
You must be signed in to change notification settings - Fork 6
Add logger plugin #8
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
<?php | ||
|
||
namespace spec\Http\Client\Plugin; | ||
|
||
use Http\Client\Exception\HttpException; | ||
use Http\Client\Exception\NetworkException; | ||
use Http\Client\Plugin\Normalizer\Normalizer; | ||
use Http\Client\Utils\Promise\FulfilledPromise; | ||
use Http\Client\Utils\Promise\RejectedPromise; | ||
use PhpSpec\ObjectBehavior; | ||
use Prophecy\Argument; | ||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Log\LoggerInterface; | ||
|
||
class LoggerPluginSpec extends ObjectBehavior | ||
{ | ||
function let(LoggerInterface $logger) | ||
{ | ||
$this->beConstructedWith($logger); | ||
} | ||
|
||
function it_is_initializable() | ||
{ | ||
$this->shouldHaveType('Http\Client\Plugin\LoggerPlugin'); | ||
} | ||
|
||
function it_is_a_plugin() | ||
{ | ||
$this->shouldImplement('Http\Client\Plugin\Plugin'); | ||
} | ||
|
||
function it_logs_request_and_response(LoggerInterface $logger, RequestInterface $request, ResponseInterface $response) | ||
{ | ||
$logger->info('Emit request: "GET / 1.1"', ['request' => $request])->shouldBeCalled(); | ||
$logger->info('Receive response: "200 Ok 1.1" for request: "GET / 1.1"', ['request' => $request, 'response' => $response])->shouldBeCalled(); | ||
|
||
$request->getMethod()->willReturn('GET'); | ||
$request->getRequestTarget()->willReturn('/'); | ||
$request->getProtocolVersion()->willReturn('1.1'); | ||
|
||
$response->getReasonPhrase()->willReturn('Ok'); | ||
$response->getProtocolVersion()->willReturn('1.1'); | ||
$response->getStatusCode()->willReturn('200'); | ||
|
||
$next = function () use ($response) { | ||
return new FulfilledPromise($response->getWrappedObject()); | ||
}; | ||
|
||
$this->handleRequest($request, $next, function () {}); | ||
} | ||
|
||
function it_logs_exception(LoggerInterface $logger, RequestInterface $request) | ||
{ | ||
$exception = new NetworkException('Cannot connect', $request->getWrappedObject()); | ||
|
||
$logger->info('Emit request: "GET / 1.1"', ['request' => $request])->shouldBeCalled(); | ||
$logger->error('Error: "Cannot connect" when emitting request: "GET / 1.1"', ['request' => $request, 'exception' => $exception])->shouldBeCalled(); | ||
|
||
$request->getMethod()->willReturn('GET'); | ||
$request->getRequestTarget()->willReturn('/'); | ||
$request->getProtocolVersion()->willReturn('1.1'); | ||
|
||
$next = function () use ($exception) { | ||
return new RejectedPromise($exception); | ||
}; | ||
|
||
$this->handleRequest($request, $next, function () {}); | ||
} | ||
|
||
function it_logs_response_within_exception(LoggerInterface $logger, RequestInterface $request, ResponseInterface $response) | ||
{ | ||
$exception = new HttpException('Forbidden', $request->getWrappedObject(), $response->getWrappedObject()); | ||
|
||
$logger->info('Emit request: "GET / 1.1"', ['request' => $request])->shouldBeCalled(); | ||
$logger->error('Error: "Forbidden" with response: "403 Forbidden 1.1" when emitting request: "GET / 1.1"', [ | ||
'request' => $request, | ||
'response' => $response, | ||
'exception' => $exception | ||
])->shouldBeCalled(); | ||
|
||
$request->getMethod()->willReturn('GET'); | ||
$request->getRequestTarget()->willReturn('/'); | ||
$request->getProtocolVersion()->willReturn('1.1'); | ||
|
||
$response->getReasonPhrase()->willReturn('Forbidden'); | ||
$response->getProtocolVersion()->willReturn('1.1'); | ||
$response->getStatusCode()->willReturn('403'); | ||
|
||
$next = function () use ($exception) { | ||
return new RejectedPromise($exception); | ||
}; | ||
|
||
$this->handleRequest($request, $next, function () {}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
namespace spec\Http\Client\Plugin\Normalizer; | ||
|
||
use PhpSpec\ObjectBehavior; | ||
use Prophecy\Argument; | ||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\UriInterface; | ||
use Psr\Log\LoggerInterface; | ||
|
||
class NormalizerSpec extends ObjectBehavior | ||
{ | ||
function it_is_initializable(LoggerInterface $logger) | ||
{ | ||
$this->shouldHaveType('Http\Client\Plugin\Normalizer\Normalizer'); | ||
} | ||
|
||
function it_normalize_request_to_string(RequestInterface $request) | ||
{ | ||
$request->getMethod()->willReturn('GET'); | ||
$request->getRequestTarget()->willReturn('/'); | ||
$request->getProtocolVersion()->willReturn('1.1'); | ||
|
||
$this->normalizeRequestToString($request)->shouldReturn('GET / 1.1'); | ||
} | ||
|
||
function it_normalize_response_to_string(ResponseInterface $response) | ||
{ | ||
$response->getReasonPhrase()->willReturn('Ok'); | ||
$response->getProtocolVersion()->willReturn('1.1'); | ||
$response->getStatusCode()->willReturn('200'); | ||
|
||
$this->normalizeResponseToString($response)->shouldReturn('200 Ok 1.1'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php | ||
|
||
namespace Http\Client\Plugin; | ||
|
||
use Http\Client\Exception; | ||
use Http\Client\Plugin\Normalizer\Normalizer; | ||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Log\LoggerInterface; | ||
|
||
/** | ||
* Log request, response and exception for a HTTP Client | ||
* | ||
* @author Joel Wurtz <joel.wurtz@gmail.com> | ||
*/ | ||
class LoggerPlugin implements Plugin | ||
{ | ||
/** | ||
* Logger to log request / response / exception for a http call | ||
* | ||
* @var LoggerInterface | ||
*/ | ||
private $logger; | ||
|
||
/** | ||
* Normalize request and response to string or array | ||
* | ||
* @var Normalizer | ||
*/ | ||
private $normalizer; | ||
|
||
/** | ||
* @param LoggerInterface $logger | ||
*/ | ||
public function __construct(LoggerInterface $logger) | ||
{ | ||
$this->logger = $logger; | ||
$this->normalizer = new Normalizer(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function handleRequest(RequestInterface $request, callable $next, callable $first) | ||
{ | ||
$this->logger->info(sprintf('Emit request: "%s"', $this->normalizer->normalizeRequestToString($request)), ['request' => $request]); | ||
|
||
return $next($request)->then(function (ResponseInterface $response) use($request) { | ||
$this->logger->info( | ||
sprintf('Receive response: "%s" for request: "%s"', $this->normalizer->normalizeResponseToString($response), $this->normalizer->normalizeRequestToString($request)), | ||
[ | ||
'request' => $request, | ||
'response' => $response, | ||
] | ||
); | ||
|
||
return $response; | ||
}, function (Exception $exception) use($request) { | ||
if ($exception instanceof Exception\HttpException) { | ||
$this->logger->error( | ||
sprintf('Error: "%s" with response: "%s" when emitting request: "%s"', $exception->getMessage(), $this->normalizer->normalizeResponseToString($exception->getResponse()), $this->normalizer->normalizeRequestToString($request)), | ||
[ | ||
'request' => $request, | ||
'response' => $exception->getResponse(), | ||
'exception' => $exception | ||
] | ||
); | ||
} else { | ||
$this->logger->error( | ||
sprintf('Error: "%s" when emitting request: "%s"', $exception->getMessage(), $this->normalizer->normalizeRequestToString($request)), | ||
[ | ||
'request' => $request, | ||
'exception' => $exception | ||
] | ||
); | ||
} | ||
|
||
throw $exception; | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?php | ||
|
||
namespace Http\Client\Plugin\Normalizer; | ||
|
||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
|
||
/** | ||
* Normalize a request or a response into a string or an array | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Author info There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you please also explain in the class docblock that this is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hum i add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is...for now. We should replace it in the future with some kind of Formatter interface with different kind of formatters. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, i see. hm. it does make sense to inject the string converter. but it will be a BC break when we start a separate tool and switch to that. could we for now simply instantiate Normalizer in the plugin and not have an argument for Normalizer? then we can add an optional argument once we have built the separate package to convert requests to strings, without creating a BC break for users. |
||
* | ||
* @author Joel Wurtz <joel.wurtz@gmail.com> | ||
* | ||
* @internal Should not be used outside of the logger plugin | ||
*/ | ||
class Normalizer | ||
{ | ||
/** | ||
* Normalize a request to string | ||
* | ||
* @param RequestInterface $request | ||
* | ||
* @return string | ||
*/ | ||
public function normalizeRequestToString(RequestInterface $request) | ||
{ | ||
return sprintf('%s %s %s', $request->getMethod(), $request->getRequestTarget(), $request->getProtocolVersion()); | ||
} | ||
|
||
/** | ||
* Normalize a response to string | ||
* | ||
* @param ResponseInterface $response | ||
* | ||
* @return string | ||
*/ | ||
public function normalizeResponseToString(ResponseInterface $response) | ||
{ | ||
return sprintf("%s %s %s", $response->getStatusCode(), $response->getReasonPhrase(), $response->getProtocolVersion()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so the promise is handling the throwing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by catching the exception, i mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes when calling one of the callable passed to the then method, if it returns something then the value will go into the next onFulfilled callable, if it throw an exception it will go into the next onRejected callable.