How do you log all API calls using Guzzle 6 - php

I'm trying to use guzzle 6 which works fine but I'm lost when it comes to how to log all the api calls. I would like to simply log timing, logged in user from session, url and any other usual pertinent info that has to do with the API call. I can't seem to find any documentation for Guzzle 6 that refers to this, only guzzle 3 (Where they've changed the logging addSubscriber call). This is how my current API calls are:
$client = new GuzzleHttp\Client(['defaults' => ['verify' => false]]);
$res = $client->get($this->url . '/api/details', ['form_params' => ['file' => $file_id]]);

You can use any logger which implements PSR-3 interface with Guzzle 6
I used Monolog as logger and builtin middleware of Guzzle with MessageFormatter in below example.
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\MessageFormatter;
use Monolog\Logger;
$stack = HandlerStack::create();
$stack->push(
Middleware::log(
new Logger('Logger'),
new MessageFormatter('{req_body} - {res_body}')
)
);
$client = new \GuzzleHttp\Client(
[
'base_uri' => 'http://httpbin.org',
'handler' => $stack,
]
);
echo (string) $client->get('ip')->getBody();
The details about the log middleware and message formatter has not well documented yet. But you can check the list which variables you can use in MessageFormatter
Also there is a guzzle-logmiddleware which allows you to customize formatter etc.

#KingKongFrog This is the way to specify the name of the log file
$logger = new Logger('MyLog');
$logger->pushHandler(new StreamHandler(__DIR__ . '/test.log'), Logger::DEBUG);
$stack->push(Middleware::log(
$logger,
new MessageFormatter('{req_body} - {res_body}')
));

For Guzzle 7 I did this::
require './guzzle_7.2.0.0/vendor/autoload.php';
require './monolog/vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\MessageFormatter;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use GuzzleHttp\TransferStats;
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
$logger = null;
$messageFormat =
//['REQUEST: ', 'METHOD: {method}', 'URL: {uri}', 'HTTP/{version}', 'HEADERS: {req_headers}', 'Payload: {req_body}', 'RESPONSE: ', 'STATUS: {code}', 'BODY: {res_body}'];
'REQUEST: urldecode(req_body)';
$handlerStack = \GuzzleHttp\HandlerStack::create();
$handlerStack->push(createGuzzleLoggingMiddleware($messageFormat));
function getLogger() {
global $logger;
if ($logger==null) {
$logger = new Logger('api-consumer');
$logger->pushHandler(new \Monolog\Handler\RotatingFileHandler('./TataAigHealthErrorMiddlewarelog.txt'));
}
var_dump($logger);
return $logger;
}
function createGuzzleLoggingMiddleware(string $messageFormat){
return \GuzzleHttp\Middleware::log(getLogger(), new \GuzzleHttp\MessageFormatter($messageFormat));
}
function createLoggingHandlerStack(array $messageFormats){
global $logger;
$stack = \GuzzleHttp\HandlerStack::create();
var_dump($logger);
collect($messageFormats)->each(function ($messageFormat) use ($stack) {
// We'll use unshift instead of push, to add the middleware to the bottom of the stack, not the top
$stack->unshift(createGuzzleLoggingMiddleware($messageFormat) );
});
return $stack;
}
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
$client = new Client(['verify' => false, 'handler' => $tapMiddleware($handlerStack)]);
WOW !!

unshift() is indeed better than push() in reverse order ...
$handlers = HandlerStack::create();
$logger = new Logger('Logger');
$templates = [
'{code} >> {req_headers}',
'{code} >> {req_body}',
'{code} << {res_headers}',
'{code} << {res_body}'
];
foreach ($templates as $template) {
$handlers->unshift($this->getMiddleware($logger, $template));
}
$client = new Client([
RequestOptions::DEBUG => false,
'handler' => $handlers
]);
Using this function to obtain the Middleware:
private function getMiddleware(Logger $logger, string $template): callable {
return Middleware::log($logger, new MessageFormatter($template));
}
Logger comes from "monolog/monolog": "^1.27.1".
And these are all supported variable substitutions.

Related

GuzzleHttp\Client calback on every response

i found this example
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
$container = [];
$history = Middleware::history($container);
$stack = HandlerStack::create();
// Add the history middleware to the handler stack.
$stack->push($history);
$client = new Client(['handler' => $stack]);
$client->request('POST', 'http://httpbin.org/post',[
'body' => 'Hello World'
]);
// Iterate over the requests and responses
foreach ($container as $transaction) {
echo (string) $transaction['request']->getBody(); // Hello World
}
im looking similar option but without manual iterate, i need on every response call my method.
like
$stack = HandlerStack::create();
$stack->lookingMethodLike_onResponse(function($request, $response){
here i want to be
})
i want do something when any response is reveived (has request and response)

Call to undefined method Goutte\Client::setClient()

I am stuck with this error...
but the client is defined.
my code like this
use Goutte\Client;
use Illuminate\Http\Request;
use GuzzleHttp\Client as GuzzleClient;
class WebScrapingController extends Controller
{
public function doWebScraping()
{
$goutteClient = new Client();
$guzzleClient = new GuzzleClient(array(
'timeout' => 60,
'verify' => false
));
$goutteClient->setClient($guzzleClient);
$crawler = $goutteClient->request('GET', 'https://duckduckgo.com/html/?q=Laravel');
$crawler->filter('.result__title .result__a')->each(function ($node) {
dump($node->text());
});
}
}
I think error from this line
$goutteClient->setClient($guzzleClient);
goutte: "^4.0" guzzle: "7.0" Laravel Framework: "6.20.4"
This answer is regarding creating instance of Goutte client, a simple PHP Web Scraper
For Version >= 4.0.0
Pass HttpClient(either guzzle httpclient , symfony httpclient) instance directly inside the instance of Goutte Client.
use Goutte\Client;
use Symfony\Component\HttpClient\HttpClient;// or use GuzzleHttp\Client as GuzzleClient;
$client = new Client(HttpClient::create(['timeout' => 60]));
// or
// $guzzleClient = new GuzzleClient(['timeout' => 60, 'verify' => false]); // pass this to Goutte Client
For Version <= 4.0.0 (i.e from 0.1.0 to 3.3.1)
use Goutte\Client;
use GuzzleHttp\Client as GuzzleClient;
$goutteClient = new Client();
$guzzleClient = new GuzzleClient(['timeout' => 60]);
$goutteClient->setClient($guzzleClient);

PHP - Guzzle Middleware

I'm using the Pole Emploi's API,but I encounter 401 error 25 minutes later, when my token expires.
I looked for a way to get a new token and retry the request, but no way for me to understand how Middlewares work, and if I should use a middleware for my needings.
On Guzzle's docs this is written :
Middleware functions return a function that accepts the next handler to invoke. This returned function then returns another function that acts as a composed handler-- it accepts a request and options, and returns a promise that is fulfilled with a response. Your composed middleware can modify the request, add custom request options, and modify the promise returned by the downstream handler.
And this is an example code from the docs :
use Psr\Http\Message\RequestInterface;
function my_middleware()
{
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
return $handler($request, $options);
};
};
}
So I think I need to manage the "promise" to see if its HTTP code is 401, and then get a new token and retry the request ?
I'm lost, so I would appreciate if someone can explain me the logic of this with different words maybe :)
Thank you in advance.
It doesn't need to be that difficult, add a handler that takes care of the job, in combination with cache that expires.
If you don't use cache then I guess you could probably save it to a file along with a timestamp for expiration that you check against when fetching it.
class AuthenticationHandler
{
private $username;
private $password;
private $token_name = 'access_token';
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
public function __invoke(callable $handler)
{
return function (RequestInterface $request, array $options) use ($handler) {
if (is_null($token = Cache::get($this->token_name))) {
$response = $this->getJWT();
Cache::put($this->token_name, $token = $response->access_token, floor($response->expires_in));
}
return $handler(
$request->withAddedHeader('Authorization', 'Bearer '.$token)
->withAddedHeader('Api-Key', $this->api_key), $options
);
};
}
private function getJWT()
{
$response = (new Client)->request('POST', 'new/token/url', [
'form_params' => [
'grant_type' => 'client_credentials',
'username' => $this->username,
'password' => $this->password,
],
]);
return json_decode($response->getBody());
}
}
Then use it:
$stack = HandlerStack::create(new CurlHandler());
$stack->push(new AuthenticationHandler('username', 'password'));
$client = new GuzzleHttp\Client([
'base_uri' => 'https://api.com',
'handler' => $stack,
]);
Now you will always have a valid token, and you will never have to worry about it ever again.
I wouldn't recommend doing this as it can become hell to debug your application and as far as I am aware Guzzle doesn't really allow access to the client from middleware. Regardless you can use Promises to get around. If I were you I would refresh token before other requests, or refresh periodically. It might be fine if you are firing requests one by one, but in a Pool it will become a nightmare because you can end up having script fetch token too often and then some request ends up with out-dated token.
Anyway here is a rough example:
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
function my_middleware()
{
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
/**
* #var $promise \GuzzleHttp\Promise\Promise
*/
$promise = $handler($request, $options);
return $promise->then(
function (ResponseInterface $response) use ($request, $options) {
if ($response->getStatusCode() === 404) {
var_dump($response->getStatusCode());
var_dump(strlen($response->getBody()));
// Pretend we are getting new token key here
$client = new Client();
$key = $client->get('https://www.iana.org/domains/reserved');
// Then we modify the failed request. For your case you use ->withHeader() to change the
// Authorization header with your token.
$uri = $request->getUri();
$uri = $uri->withHost('google.com')->withPath('/');
// New instance of Request
$request = $request->withUri($uri);
// Send the request again with our new header/URL/whatever
return $client->sendAsync($request, $options);
}
return $response;
}
);
};
};
}
$handlerStack = HandlerStack::create();
$handlerStack->push(my_middleware());
$client = new Client([
'base_uri' => 'https://example.org',
'http_errors' => false,
'handler' => $handlerStack
]);
$options = [];
$response = $client->request('GET', '/test', $options);
var_dump($response->getStatusCode());
var_dump(strlen($response->getBody()));
echo $response->getBody();

XML Raw Response using ebay-sdk-php

I want to get Raw XML Response from this code. But I am getting Object Representation. I like to store the XML Response in a file. I hope there is an workaround.
<?php
//REQUIRED FILES INCLUSION
require_once(__DIR__.'/../../vendor/autoload.php');
require_once(__DIR__.'/../../../Config/Config.php');
//require_once(__DIR__.'/../../../Helper.php');
//NAMESPACE
use \DTS\eBaySDK\Constants;
use \DTS\eBaySDK\Trading\Services;
use \DTS\eBaySDK\Trading\Types;
use \DTS\eBaySDK\Trading\Enums;
//SERVICE CREATION
$Service = new Services\TradingService([
'credentials' => $Config['production']['credentials'],
'sandbox' => false,
'siteId' => Constants\SiteIds::MOTORS,
'httpOptions' => [
'verify' => false
]
]);
//CATEGORY PARAMETERS
$Parameters=array(
//'DetailLevel' => array('ItemReturnCategories'),
'DetailLevel' => array('ReturnAll'),
'WarningLevel' => 'High'
);
//REQUEST
$Request = new Types\GetCategoriesRequestType($Parameters);
$Request->RequesterCredentials = new Types\CustomSecurityHeaderType();
$Request->RequesterCredentials->eBayAuthToken = $Config['production']['authToken'];
$Response = $Service->getCategories($Request);
print_r($Response);
It is possible to pass your own HTTP handler to the SDK via the httpHandler configuration option. This means you can intercept the raw response body before letting the SDK parse it.
The example below shows how to create a simple handler that uses Guzzle to send and process the response. The class is able to save it to a file that you specify. This is better than using the toRequestXml method as that does not give you the actual XML sent by eBay. It gets the object to generate the XML and therefore will be different to the eBay response.
<?php
require __DIR__.'/vendor/autoload.php';
$config = require __DIR__.'/configuration.php';
use \DTS\eBaySDK\Constants;
use \DTS\eBaySDK\Trading\Services;
use \DTS\eBaySDK\Trading\Types;
use \DTS\eBaySDK\Trading\Enums;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class ResponseLogger
{
private $client;
private $logPath;
public function __construct($logPath)
{
$this->logPath = $logPath;
$this->client = new Client();
}
/**
* This will be called by the SDK and will handle sending the request to the API
* Because of this it will be able to handle saving the response to a file.
*/
public function __invoke(RequestInterface $request, array $options)
{
return $this->client->sendAsync($request)->then(
function (ResponseInterface $response) use ($request) {
$stream = $response->getBody();
file_put_contents($this->logPath, $stream);
/**
* We have to rewind to the start of the steam before giving back to the SDK to process!
* If we don't the SDK will try and parse from the end of the response body.
*/
$stream->rewind();
return $response;
}
);
}
}
$service = new Services\TradingService([
'credentials' => $config['production']['credentials'],
'authToken' => $config['production']['authToken'],
'siteId' => Constants\SiteIds::MOTORS,
'httpHandler' => new ResponseLogger(__DIR__.'/categories.xml')
]);
$response = $service->getCategories(
new Types\GetCategoriesRequestType([
'DetailLevel' => ['ReturnAll'],
'WarningLevel' => 'High'
])
);
if (isset($response->Errors)) {
foreach ($response->Errors as $error) {
printf(
"%s: %s\n%s\n\n",
$error->SeverityCode === Enums\SeverityCodeType::C_ERROR ? 'Error' : 'Warning',
$error->ShortMessage,
$error->LongMessage
);
}
}
I haven't used this package before, but looking at the code on GitHub it looks like \DTS\eBaySDK\Trading\Services\TradingService::getCategories returns an instance of \DTS\eBaySDK\Types\BaseType which contains a method called toRequestXml which you might be able to use.
From GitHub:
/**
* Converts the object to a XML request string.
*
* #return string The XML request string.
*/
public function toRequestXml()
{
return $this->toXml(self::$requestXmlRootElementNames[get_class($this)], true);
}

Mock response and use history middleware at the same time in Guzzle

Is there any way to mock response and request in Guzzle?
I have a class which sends some request and I want to test.
In Guzzle doc I found a way how can I mock response and request separately. But how can I combine them?
Because, If use history stack, guzzle trying to send a real request.
And visa verse, when I mock response handler can't test request.
class MyClass {
public function __construct($guzzleClient) {
$this->client = $guzzleClient;
}
public function registerUser($name, $lang)
{
$body = ['name' => $name, 'lang' = $lang, 'state' => 'online'];
$response = $this->sendRequest('PUT', '/users', ['body' => $body];
return $response->getStatusCode() == 201;
}
protected function sendRequest($method, $resource, array $options = [])
{
try {
$response = $this->client->request($method, $resource, $options);
} catch (BadResponseException $e) {
$response = $e->getResponse();
}
$this->response = $response;
return $response;
}
}
Test:
class MyClassTest {
//....
public function testRegisterUser()
{
$guzzleMock = new \GuzzleHttp\Handler\MockHandler([
new \GuzzleHttp\Psr7\Response(201, [], 'user created response'),
]);
$guzzleClient = new \GuzzleHttp\Client(['handler' => $guzzleMock]);
$myClass = new MyClass($guzzleClient);
/**
* But how can I check that request contains all fields that I put in the body? Or if I add some extra header?
*/
$this->assertTrue($myClass->registerUser('John Doe', 'en'));
}
//...
}
#Alex Blex was very close.
Solution:
$container = [];
$history = \GuzzleHttp\Middleware::history($container);
$guzzleMock = new \GuzzleHttp\Handler\MockHandler([
new \GuzzleHttp\Psr7\Response(201, [], 'user created response'),
]);
$stack = \GuzzleHttp\HandlerStack::create($guzzleMock);
$stack->push($history);
$guzzleClient = new \GuzzleHttp\Client(['handler' => $stack]);
First of all, you don't mock requests. The requests are the real ones you are going to use in production. The mock handler is actually a stack, so you can push multiple handlers there:
$container = [];
$history = \GuzzleHttp\Middleware::history($container);
$stack = \GuzzleHttp\Handler\MockHandler::createWithMiddleware([
new \GuzzleHttp\Psr7\Response(201, [], 'user created response'),
]);
$stack->push($history);
$guzzleClient = new \GuzzleHttp\Client(['handler' => $stack]);
After you run your tests, $container will have all transactions for you to assert. In your particular test - a single transaction. You are interested in $container[0]['request'], since $container[0]['response'] will contain your canned response, so there is nothing to assert really.

Categories