Laravel - Log Guzzle requests to file
Asked Answered
C

3

3

While working on a project I found third party API's are working from Postman, but doesn't work from Guzzle Client.

Debugging a Guzzle request could be difficult, so is there any way that can log all requests made by Guzzle client can be seen?

Conducive answered 17/8, 2019 at 8:27 Comment(0)
C
7

TLDR;

There’s an easy way to log all Guzzle requests by passing second parameter to Client and then it will log all the requests. But that’s ugly way if you have many methods using Guzzle Client to send request to third party server. I’ve done it using Laravel’s Service Container.

Long way via Laravel’s Service Container

When I used Guzzle client in my project and used handler to log all requests it looks good. But later on there were many methods in many different classes so I have to write logger logic every where. Then I thought why don’t to leverage Laravel’s Service Container and bind an object once and use it everywhere.

Here’s how I did it. In your AppServiceContainer.php’s boot method we will add all our code. And then in Controllers we will use our Client object.

Add this use statements on top of the AppServiceContainer.php file.

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
use Illuminate\Support\ServiceProvider;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;

Add below code to your AppServiceContainer.php’s boot method

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
    {
        $this->app->bind('GuzzleClient', function () {

            $messageFormats = [
                'REQUEST: {method} - {uri} - HTTP/{version} - {req_headers} - {req_body}',
                'RESPONSE: {code} - {res_body}',
            ];

            $stack = 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(
                    Middleware::log(
                        with(new Logger('guzzle-log'))->pushHandler(
                            new RotatingFileHandler(storage_path('logs/guzzle-log.log'))
                        ),
                        new MessageFormatter($messageFormat)
                    )
                );
            });

            return function ($config) use ($stack){
                return new Client(array_merge($config, ['handler' => $stack]));
            };
        });
    }

Explanation

If you have noticed above code, In first line of boot method we are telling Laravel that we want to register this code as a GuzzleClient in your Service Container.

In last return statement we are returning a function that will accept one argument $config. We used this function as a proxy so that we can pass an argument to it and that can be used in Client Object.

return function ($config) use ($stack){
      return new Client(array_merge($config, ['handler' => $stack]));
};

Rest of the code is building Guzzle’s handler object to Log all requests to a file called guzzle-log.log using Logger object of Monolog library. If you have daily logs enabled, a date will be appended to file name like guzzle-log-2019-08-11.log. Usage

We have binded our object to Service Container, now it’s time to use this container everywhere in our code, and make it looks clean.

For demo purpose I’ve used it directly in routes/web.php file. You can use anywhere.

 Route::get('/', function () {

    $client = app('GuzzleClient')(['base_uri' => 'http://httpbin.org/']);

    $request = $client->get('get',[
        'query' => ['foo'=>'bar', 'baz' => 'baz2'] ,
        'headers' => [ 'accept' =>  'application/json']
    ]);
    $response = json_decode((string) $request->getBody());
    return response()->json($response);
});

As you can see I’m making an object $client using app() helper. Also you can pass any valid arguments array that Guzzle client supports as a second parameter. Here I’ve passed base_uri.

Log file entry

Source: http://shyammakwana.me/laravel/laravel-log-guzzle-requests-to-file-using-service-container.html

Conducive answered 17/8, 2019 at 8:27 Comment(1)
I am using Guzzle Http in laravel 7 can you please help me how I achieve in this same This is my posted question #67920540Elysha
K
3

The accepted answer works nicely by using Service Providers. Another option would be to attach GuzzleHttp\Middleware's log to wherever you use Illuminate\Support\Facades\Http. An example which logs the request, the response, and any errors if found would be to use Middleware::log:

<?php

namespace App\Services;


use Illuminate\Support\Facades\Http;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;

/**
 * Class TestService
 * @package App
 */
class TestService
{
    private function getAccessToken()
    {
        try {
            $response = Http::asForm()->withMiddleware(Middleware::log(with(new Logger('guzzle-log'))->pushHandler(
                new RotatingFileHandler(storage_path('logs/guzzle-log.log'))
            ), new MessageFormatter(MessageFormatter::DEBUG)))->post("https://test.com/oauth/v2/token", [
                'grant_type' => 'client_credentials',
            ]);
            $response->throw();
        } catch (\Throwable $th) {
            $accessToken = false;
        }
        return $accessToken;
    }
}

This will write log records to logs/guzzle-log-{currentDate}.log file. The format of the log record I used in this example is MessageFormatter::DEBUG which is ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}" which nicely outputs the request, response, and any errors. an example of a log file would be:

[2020-08-07T07:13:23.712124+00:00] guzzle-log.INFO: >>>>>>>> POST /oauth/v2/token HTTP/1.1 Content-Length: 29 User-Agent: GuzzleHttp/7 Host: xxxx:4000 Content-Type: application/x-www-form-urlencoded  grant_type=client_credentials <<<<<<<< HTTP/1.1 200 OK X-DNS-Prefetch-Control: off X-Frame-Options: SAMEORIGIN Strict-Transport-Security: max-age=15552000; includeSubDomains X-Download-Options: noopen X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Access-Control-Allow-Origin: * Content-Type: application/json; charset=utf-8 Content-Length: 113 ETag: W/"71-DyA+KEnetTKfUlb0lznokGTt0qk" Date: Fri, 07 Aug 2020 07:13:23 GMT Connection: keep-alive  {"data":{"token_type":"Bearer","access_token":"XYZ","expires_in":"7776000"}} -------- NULL [] []

Note: The downside to this option is that you will have to attach withMiddleware(Middleware::log(...)) to anywhere you are using Illuminate\Support\Facades\Http.

Kolnick answered 7/8, 2020 at 7:17 Comment(1)
thank you, this was exactly what i was looking forIsopropyl
S
0

If you aren't able to get the response content after logging, the answer of @shyammakwana.me may be extended by adding the following:

use Psr\Http\Message\ResponseInterface;

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(
        Middleware::log(
            with(new Logger('guzzle-log'))->pushHandler(
                new RotatingFileHandler(storage_path('logs/guzzle-log.log'))
            ),
            new MessageFormatter($messageFormat)
        )
    );
});

// Rewind the stream to return the content

$mapResponse = Middleware::mapResponse(function (ResponseInterface $response) {
    $response->getBody()->rewind();
    return $response;
});
$stack->unshift($mapResponse);

Reference: Empty body when debugging request with Middleware::log

Sosna answered 19/10, 2023 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.