SECRET OF CSS

How To Log Guzzle Requests. A simpler way to update your middleware | by Michael Stivala | Sep, 2022


A simpler way to update your middleware

1*WJCtL6GS4oenfvbQ44Se2g

When you’re building an API consumer, you should log your API requests and responses. Otherwise, it can feel like you’re working blind.

I’ll explain how to configure a Guzzle middleware to log requests to a file. This is useful for debugging and historical purposes.

The way to do this is to pass a new HandlerStack with our logging middleware into our Client instance.

The syntax for creating a new Guzzle instance is as follows:

$client = new \GuzzleHttp\Client([
'base_uri' => "http://www.example.com/api",
'handler' => $handlerStack,
]),
]);

The $handlerStack will eventually hold the middleware that handles the logging.

Before setting up the middleware, I need to instantiate something that handles writing our log messages to file. I’ll use the Monolog package to handle this. It makes it easy to achieve my desired criteria of having a daily log file next to my default laravel.log.

In my AppServiceProvider, I’ll set up a helper function to create the logger:

private function getLogger()
{
if (! $this->logger) {
$this->logger = with(new \Monolog\Logger('api-consumer'))->pushHandler(
new \Monolog\Handler\RotatingFileHandler(storage_path('logs/api-consumer.log'))
);
}
return $this->logger;
}

Guzzle’s logging middleware requires a logger (that I’ve just created above) and a MessageFormatter instance that controls what gets logged.

I’ll set up a helper function to create the middleware:

private function createGuzzleLoggingMiddleware(string $messageFormat)
{
return \GuzzleHttp\Middleware::log(
$this->getLogger(),
new \GuzzleHttp\MessageFormatter($messageFormat)
);
}

The $messageFormat is a string that “Formats log messages using variable substitutions for requests, responses, and other transactional data.” Possible substitutions can be seen here — but I’ve come up with some handy ones:

Logging the request: {method} {uri} HTTP/{version} {req_body}
Logging the response: RESPONSE: {code} — {res_body}

Now that I have a way to easily new up an instance of Guzzle’s logging middleware, I can create a new HandlerStack to push the middleware onto:

$handlerStack = \GuzzleHttp\HandlerStack::create();$handlerStack->push(
$this->createGuzzleLoggingMiddleware($messageFormat)
);

I decided to log the request and response in two separate log entries. It seems like the only way to accomplish this is to push multiple logging middlewares to Guzzle, so I set up a helper function to create a HandlerStack with all the required logging middleware from an array of message format strings that I would like to log.

private function createLoggingHandlerStack(array $messageFormats)
{
$stack = \GuzzleHttp\HandlerStack::create();
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(
$this->createGuzzleLoggingMiddleware($messageFormat)
);
});
return $stack;
}

And there we have it! Now, adding multiple loggers to our Guzzle client is as easy as the code below:

$client = new Client([
'base_uri' => "http://example.com",
'handler' => $this->createLoggingHandlerStack([
'{method} {uri} HTTP/{version} {req_body}',
'RESPONSE: {code} - {res_body}',
]),
]);

After setting up the logger, I realised that I could no longer access the response body — it turns out it was my mistake.

Previously, I was accessing the response content using the following line:

$this->client->get($endpoint)->getBody()->getContents();

But it turns out that this gets the “remaining contents of the body as a string” — and since the middleware already accessed the body to log it, there isn’t any content remaining to retrieve.

The correct way to access the response content is to cast the body to a string:

(string) $this->client->get($endpoint)->getBody();



News Credit

%d bloggers like this: