1
0
mirror of https://github.com/guzzle/guzzle.git synced 2025-02-25 02:22:57 +01:00

Renaming BackoffPlugin to RetryPlugin and fixing MessageFormatter

RetryPlugin now supports logging retries by intercepting the retry delay
function.

Updated the MessageFormatter to properly log request and response strings.
This commit is contained in:
Michael Dowling 2014-02-08 17:28:01 -08:00
parent 2a2017bef1
commit 0e6a6c3d26
3 changed files with 118 additions and 94 deletions

View File

@ -1,28 +0,0 @@
{
"name": "guzzle/plugin-backoff",
"description": "Guzzle backoff retry plugins",
"homepage": "http://guzzlephp.org/",
"keywords": ["plugin", "guzzle"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=5.3.2",
"guzzle/http": "self.version",
"guzzle/log": "self.version"
},
"autoload": {
"psr-0": { "Guzzle\\Plugin\\Backoff": "" }
},
"target-dir": "Guzzle/Plugin/Backoff",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
}
}

View File

@ -6,30 +6,30 @@ use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\ResponseInterface;
/**
* Message formatter used in various places in the framework
* Formats messages using variable substitutions for requests, responses, and other transactional data.
*
* Format messages using a template that can contain the the following variables:
* The following variable substitutions are supported:
*
* - {request}: Full HTTP request message
* - {response}: Full HTTP response message
* - {ts}: Timestamp
* - {host}: Host of the request
* - {method}: Method of the request
* - {url}: URL of the request
* - {host}: Host of the request
* - {protocol}: Request protocol
* - {version}: Protocol version
* - {resource}: Resource of the request (path + query + fragment)
* - {hostname}: Hostname of the machine that sent the request
* - {code}: Status code of the response (if available)
* - {phrase}: Reason phrase of the response (if available)
* - {error}: Any error messages (if available)
* - {req_header_*}: Replace `*` with the lowercased name of a request header to add to the message
* - {res_header_*}: Replace `*` with the lowercased name of a response header to add to the message
* - {req_headers}: Request headers
* - {res_headers}: Response headers
* - {req_body}: Request body
* - {res_body}: Response body
* - {request}: Full HTTP request message
* - {response}: Full HTTP response message
* - {ts}: Timestamp
* - {host}: Host of the request
* - {method}: Method of the request
* - {url}: URL of the request
* - {host}: Host of the request
* - {protocol}: Request protocol
* - {version}: Protocol version
* - {resource}: Resource of the request (path + query + fragment)
* - {hostname}: Hostname of the machine that sent the request
* - {code}: Status code of the response (if available)
* - {phrase}: Reason phrase of the response (if available)
* - {error}: Any error messages (if available)
* - {req_header_*}: Replace `*` with the lowercased name of a request header to add to the message
* - {res_header_*}: Replace `*` with the lowercased name of a response header to add to the message
* - {req_headers}: Request headers
* - {res_headers}: Response headers
* - {req_body}: Request body
* - {res_body}: Response body
*/
class MessageFormatter
{
@ -38,7 +38,7 @@ class MessageFormatter
const SHORT_FORMAT = '[{ts}] "{method} {resource} {protocol}/{version}" {code}';
/** @var string Template used to format log messages */
protected $template;
private $template;
/**
* @param string $template Log message template
@ -48,20 +48,6 @@ class MessageFormatter
$this->template = $template ?: self::DEFAULT_FORMAT;
}
/**
* Set the template to use for logging
*
* @param string $template Log message template
*
* @return self
*/
public function setTemplate($template)
{
$this->template = $template;
return $this;
}
/**
* Returns a formatted message
*
@ -97,10 +83,18 @@ class MessageFormatter
$result = $response;
break;
case 'req_headers':
$result = $request->getStartLine() . "\r\n" . $request->getHeaders();
$result = trim($request->getMethod() . ' '
. $request->getResource()) . ' HTTP/'
. $request->getProtocolVersion() . "\r\n"
. $request->getHeaders();
break;
case 'res_headers':
$result = $response->getStartLine() . "\r\n" . $response->getHeaders();
$result = sprintf(
'HTTP/%s %d %s',
$response->getProtocolVersion(),
$response->getStatusCode(),
$response->getReasonPhrase()
) . "\r\n" . $response->getHeaders();
break;
case 'req_body':
$result = $request->getBody();
@ -133,13 +127,13 @@ class MessageFormatter
$result = gethostname();
break;
case 'code':
$result = $response ? $response->getStatusCode() : '';
$result = $response ? $response->getStatusCode() : 'NULL';
break;
case 'phrase':
$result = $response ? $response->getReasonPhrase() : '';
$result = $response ? $response->getReasonPhrase() : 'NULL';
break;
case 'error':
$result = $error ? $error->getMessage() : null;
$result = $error ? $error->getMessage() : 'NULL';
break;
default:
if (strpos($matches[1], 'req_header_') === 0) {

View File

@ -1,16 +1,23 @@
<?php
namespace Guzzle\Plugin\Backoff;
namespace Guzzle\Plugin\Retry;
use Guzzle\Common\EventSubscriberInterface;
use Guzzle\Http\Event\AbstractTransferStatsEvent;
use Guzzle\Http\Event\RequestErrorEvent;
use Guzzle\Http\Event\RequestEvents;
use Guzzle\Plugin\Log\MessageFormatter;
use Guzzle\Plugin\Log\SimpleLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* Plugin to automatically retry failed HTTP requests using a backoff strategy
* Plugin to automatically retry failed HTTP requests using filters a delay strategy.
*/
class BackoffPlugin implements EventSubscriberInterface
class RetryPlugin implements EventSubscriberInterface
{
const RETRY_FORMAT = '[{ts}] {method} {url} - {code} {phrase} - Retries: {retries}, Delay: {delay}, Time: {connect_time}, {total_time}, Error: {error}';
/** @var callable */
private $filter;
@ -20,10 +27,20 @@ class BackoffPlugin implements EventSubscriberInterface
/** @var int */
private $maxRetries;
public static function getSubscribedEvents()
{
return [
RequestEvents::AFTER_SEND => ['onRequestSent'],
RequestEvents::ERROR => ['onRequestSent']
];
}
/**
* @param callable $filter Filter used to determine whether or not to retry a request
* @param callable $delayFunc Callable that accepts the number of retries and returns the amount of
* of time in seconds to delay.
* @param callable $filter Filter used to determine whether or not to retry a request. The filter must be a
* callable that accepts the current number of retries and an AbstractTransferStatsEvent
* object. The filter must return true or false to denote if the request must be retried.
* @param callable $delayFunc Callable that accepts the number of retries and an AbstractTransferStatsEvent and
* returns the amount of of time in seconds to delay.
* @param int $maxRetries Maximum number of retries
*/
public function __construct(
@ -39,35 +56,43 @@ class BackoffPlugin implements EventSubscriberInterface
/**
* Retrieve a basic truncated exponential backoff plugin that will retry HTTP errors and cURL errors
*
* @param int $maxRetries Maximum number of retries
* @param array $httpCodes HTTP response codes to retry
* @param array $curlCodes cURL error codes to retry
* @param int $maxRetries Maximum number of retries
* @param array $httpCodes HTTP response codes to retry
* @param array $curlCodes cURL error codes to retry
* @param LoggerInterface|bool $logger Pass a logger instance to log each retry or pass true for STDOUT
* @param string|MessageFormatter $formatter Pass a message formatter if logging to customize log messages
*
* @return self
* @throws \InvalidArgumentException if logger is not a boolean or LoggerInterface
*/
public static function getExponentialBackoff(
$maxRetries = 3,
array $httpCodes = null,
array $curlCodes = null
array $curlCodes = null,
$logger = null,
$formatter = null
) {
if (extension_loaded('curl')) {
if (!extension_loaded('curl')) {
$filter = self::createStatusFilter($httpCodes);
} else {
$filter = self::createChainFilter([
self::createStatusFilter($httpCodes),
self::createCurlFilter($curlCodes)
]);
} else {
$filter = self::createStatusFilter($httpCodes);
}
return new self($filter, array(__CLASS__, 'exponentialDelay'), $maxRetries);
}
$delay = [__CLASS__, 'exponentialDelay'];
public static function getSubscribedEvents()
{
return [
RequestEvents::AFTER_SEND => ['onRequestSent'],
RequestEvents::ERROR => ['onRequestSent']
];
if ($logger) {
if ($logger === true) {
$logger = new SimpleLogger(STDOUT);
} elseif (!($logger instanceof LoggerInterface)) {
throw new \InvalidArgumentException('$logger must be true, false, or a LoggerInterface');
}
$delay = self::createLoggingDelay($delay, $logger, $formatter);
}
return new self($filter, $delay, $maxRetries);
}
public function onRequestSent(AbstractTransferStatsEvent $event)
@ -79,7 +104,7 @@ class BackoffPlugin implements EventSubscriberInterface
$filterFn = $this->filter;
if ($filterFn($retries, $event)) {
$delayFn = $this->delayFunc;
sleep($delayFn($retries));
sleep($delayFn($retries, $event));
$request->getConfig()->set('retries', ++$retries);
$event->intercept($event->getClient()->send($request));
}
@ -89,15 +114,48 @@ class BackoffPlugin implements EventSubscriberInterface
/**
* Returns an exponential delay calculation
*
* @param int $retries Number of retries so far
* @param int $retries Number of retries so far
* @param AbstractTransferStatsEvent $event Event containing transactional information
*
* @return int
*/
public static function exponentialDelay($retries)
public static function exponentialDelay($retries, AbstractTransferStatsEvent $event)
{
return (int) pow(2, $retries - 1);
}
/**
* Creates a delay function that logs each retry before proxying to a wrapped delay function.
*
* @param callable $delayFn Delay function to proxy to
* @param LoggerInterface $logger Logger used to log messages
* @param string|MessageFormatter $formatter Format string or Message formatter used to format messages
*
* @return callable
*/
public static function createLoggingDelay(
callable $delayFn,
LoggerInterface $logger,
$formatter = null
) {
if (!$formatter) {
$formatter = new MessageFormatter(self::RETRY_FORMAT);
} elseif (!($formatter instanceof MessageFormatter)) {
$formatter = new MessageFormatter($formatter);
}
return function ($retries, AbstractTransferStatsEvent $event) use ($delayFn, $logger, $formatter) {
$delay = $delayFn($retries, $event);
$logger->log(LogLevel::NOTICE, $formatter->format(
$event->getRequest(),
$event->getResponse(),
$event instanceof RequestErrorEvent ? $event->getException() : null,
['retries' => $retries + 1, 'delay' => $delay] + $event->getTransferInfo()
));
return $delay;
};
}
/**
* Creates a retry filter based on HTTP status codes
*