Laravel Logging Guzzle Requests in File
In this article, we will discuss “Laravel Logging Guzzle Requests in File”. In the multiple scenarios, we have to use third-party API’s in our project and test those API using postman or any other tool. But also required to maintain a log for each request, such as what we send (request)? and what we receive (response)? Today, I will try to explain to you how can we maintain the log for each Guzzle Client request.
There are multiple ways to do this, but the question is which one is easy to use when a project used multiple methods based on Guzzle Client.
Table of Contents
Create a Custom Service Provider
You can use the following artisan command to create a service provider.
php artisan make:provider GuzzleClientServiceProvider
After executing the above mention command, GuzzleClientServiceProvider.php has been created and located at app\Providers.
Register Servier Provider
Register newly created service provider in config/app.php.
'providers' => [ ... // Custom Service Provider App\Providers\GuzzleClientServiceProvider::class, ... ],
Setup the Logger
Here we handle the writing our log messages to the file. We are using the Monolog package to handle this.
I’ll set up a helper function “get_logger” to create the logger:
/** * Setup Logger */ private function get_logger() { if (! $this->logger) { $this->logger = with(new Logger('guzzle-log'))->pushHandler( new RotatingFileHandler(storage_path('logs/guzzle-log.log')) ); } return $this->logger; }
Setup the Middleware
Guzzle’s logging middleware requires a logger and a MessageFormatter instance that controls what gets logged. The “$messageFormat” is a string that formats log messages using variable substitutions for requests, responses, and other transactional data.
I’ll set up a helper function “setGuzzleMiddleware” to create the middleware:
/** * Setup Middleware */ private function setGuzzleMiddleware(string $messageFormat) { return Middleware::log( $this->get_logger(), new MessageFormatter($messageFormat) ); }
Setup the Logging Handler Stack
Here, I set up a helper function “setLoggingHandler” to create a HandlerStack with all the required logging middleware from an array of message format strings that I would like to log.
/** * Setup Logging Handler Stack */ private function setLoggingHandler(array $messageFormats) { $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( $this->setGuzzleMiddleware($messageFormat) ); }); return $stack; }
Update Boot Function
In the first line of “boot” method, we are telling Laravel that we want to register this code as a “GuzzleClient” in your Service Container. Then you see the “$messageFormats” array, which defines the logging format for our log file.
At last, the 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.
The 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–2020–04–06.log.
/** * Bootstrap services. * * @return void */ public function boot() { // Bind GuzzleClient $this->app->bind('GuzzleClient', function () { $messageFormats = [ 'REQUEST: ', 'METHOD: {method}', 'URL: {uri}', 'HTTP/{version}', 'HEADERS: {req_headers}', 'Payload: {req_body}', 'RESPONSE: ', 'STATUS: {code}', 'BODY: {res_body}', ]; $stack = $this->setLoggingHandler($messageFormats); return function ($config) use ($stack){ return new Client(array_merge($config, ['handler' => $stack])); }; }); }
Finally, complete GuzzleClientServiceProvider.php looks like:
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Schema; use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use GuzzleHttp\MessageFormatter; use GuzzleHttp\Middleware; use Monolog\Handler\RotatingFileHandler; use Monolog\Logger; class GuzzleClientServiceProvider extends ServiceProvider { private $logger; /** * Register services. * * @return void */ public function register() { // } /** * Bootstrap services. * * @return void */ public function boot() { // Bind GuzzleClient $this->app->bind('GuzzleClient', function () { $messageFormats = [ 'REQUEST: ', 'METHOD: {method}', 'URL: {uri}', 'HTTP/{version}', 'HEADERS: {req_headers}', 'Payload: {req_body}', 'RESPONSE: ', 'STATUS: {code}', 'BODY: {res_body}', ]; $stack = $this->setLoggingHandler($messageFormats); return function ($config) use ($stack){ return new Client(array_merge($config, ['handler' => $stack])); }; }); } /** * Setup Logger */ private function get_logger() { if (! $this->logger) { $this->logger = with(new Logger('guzzle-log'))->pushHandler( new RotatingFileHandler(storage_path('logs/guzzle-log.log')) ); } return $this->logger; } /** * Setup Middleware */ private function setGuzzleMiddleware(string $messageFormat) { return Middleware::log( $this->get_logger(), new MessageFormatter($messageFormat) ); } /** * Setup Logging Handler Stack */ private function setLoggingHandler(array $messageFormats) { $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( $this->setGuzzleMiddleware($messageFormat) ); }); return $stack; } }
Test Laravel Logging Guzzle Requests
For testing, I’ve used it directly in routes/web.php file. You can use anywhere.
// Guzzle tes route for logging Route::get('/guzzle-logger-test', function () { // Using "app(GuzzleClient)" as per the GuzzleClientServiceProvider $client = app('GuzzleClient')([ 'base_uri' => 'https://jsonplaceholder.typicode.com/', 'timeout' => 10, 'verify' => false, ]); $request = $client->get('users', [ 'headers' => [ 'accept' => 'application/json'] ]); $response = json_decode((string) $request->getBody()); return response()->json($response); });
Here’s the log file located at “storage/logs/guzzle-log-2020-04-06.log”.
[2020-04-06T18:18:58.293783+00:00] guzzle-log.INFO: REQUEST: [] [] [2020-04-06T18:18:58.298784+00:00] guzzle-log.INFO: METHOD: GET [] [] [2020-04-06T18:18:58.298957+00:00] guzzle-log.INFO: URL: https://jsonplaceholder.typicode.com/users [] [] [2020-04-06T18:18:58.299090+00:00] guzzle-log.INFO: HTTP/1.1 [] [] [2020-04-06T18:18:58.299229+00:00] guzzle-log.INFO: HEADERS: GET /users HTTP/1.1 User-Agent: GuzzleHttp/6.5.1 curl/7.67.0 PHP/7.4.1 Host: jsonplaceholder.typicode.com accept: application/json [] [] [2020-04-06T18:18:58.299374+00:00] guzzle-log.INFO: Payload: [] [] [2020-04-06T18:18:58.299490+00:00] guzzle-log.INFO: RESPONSE: [] [] [2020-04-06T18:18:58.299603+00:00] guzzle-log.INFO: STATUS: 200 [] [] [2020-04-06T18:18:58.300660+00:00] guzzle-log.INFO: BODY: [ { "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "Sincere@april.biz", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" } }, { "id": 2, "name": "Ervin Howell", "username": "Antonette", "email": "Shanna@melissa.tv", "address": { "street": "Victor Plains", "suite": "Suite 879", "city": "Wisokyburgh", "zipcode": "90566-7771", "geo": { "lat": "-43.9509", "lng": "-34.4618" } }, "phone": "010-692-6593 x09125", "website": "anastasia.net", "company": { "name": "Deckow-Crist", "catchPhrase": "Proactive didactic contingency", "bs": "synergize scalable supply-chains" } }, { "id": 3, "name": "Clementine Bauch", "username": "Samantha", "email": "Nathan@yesenia.net", "address": { "street": "Douglas Extension", "suite": "Suite 847", "city": "McKenziehaven", "zipcode": "59590-4157", "geo": { "lat": "-68.6102", "lng": "-47.0653" } }, "phone": "1-463-123-4447", "website": "ramiro.info", "company": { "name": "Romaguera-Jacobson", "catchPhrase": "Face to face bifurcated interface", "bs": "e-enable strategic applications" } }, { "id": 4, "name": "Patricia Lebsack", "username": "Karianne", "email": "Julianne.OConner@kory.org", "address": { "street": "Hoeger Mall", "suite": "Apt. 692", "city": "South Elvis", "zipcode": "53919-4257", "geo": { "lat": "29.4572", "lng": "-164.2990" } }, "phone": "493-170-9623 x156", "website": "kale.biz", "company": { "name": "Robel-Corkery", "catchPhrase": "Multi-tiered zero tolerance productivity", "bs": "transition cutting-edge web services" } }, { "id": 5, "name": "Chelsey Dietrich", "username": "Kamren", "email": "Lucio_Hettinger@annie.ca", "address": { "street": "Skiles Walks", "suite": "Suite 351", "city": "Roscoeview", "zipcode": "33263", "geo": { "lat": "-31.8129", "lng": "62.5342" } }, "phone": "(254)954-1289", "website": "demarco.info", "company": { "name": "Keebler LLC", "catchPhrase": "User-centric fault-tolerant solution", "bs": "revolutionize end-to-end systems" } }, { "id": 6, "name": "Mrs. Dennis Schulist", "username": "Leopoldo_Corkery", "email": "Karley_Dach@jasper.info", "address": { "street": "Norberto Crossing", "suite": "Apt. 950", "city": "South Christy", "zipcode": "23505-1337", "geo": { "lat": "-71.4197", "lng": "71.7478" } }, "phone": "1-477-935-8478 x6430", "website": "ola.org", "company": { "name": "Considine-Lockman", "catchPhrase": "Synchronised bottom-line interface", "bs": "e-enable innovative applications" } }, { "id": 7, "name": "Kurtis Weissnat", "username": "Elwyn.Skiles", "email": "Telly.Hoeger@billy.biz", "address": { "street": "Rex Trail", "suite": "Suite 280", "city": "Howemouth", "zipcode": "58804-1099", "geo": { "lat": "24.8918", "lng": "21.8984" } }, "phone": "210.067.6132", "website": "elvis.io", "company": { "name": "Johns Group", "catchPhrase": "Configurable multimedia task-force", "bs": "generate enterprise e-tailers" } }, { "id": 8, "name": "Nicholas Runolfsdottir V", "username": "Maxime_Nienow", "email": "Sherwood@rosamond.me", "address": { "street": "Ellsworth Summit", "suite": "Suite 729", "city": "Aliyaview", "zipcode": "45169", "geo": { "lat": "-14.3990", "lng": "-120.7677" } }, "phone": "586.493.6943 x140", "website": "jacynthe.com", "company": { "name": "Abernathy Group", "catchPhrase": "Implemented secondary concept", "bs": "e-enable extensible e-tailers" } }, { "id": 9, "name": "Glenna Reichert", "username": "Delphine", "email": "Chaim_McDermott@dana.io", "address": { "street": "Dayna Park", "suite": "Suite 449", "city": "Bartholomebury", "zipcode": "76495-3109", "geo": { "lat": "24.6463", "lng": "-168.8889" } }, "phone": "(775)976-6794 x41206", "website": "conrad.com", "company": { "name": "Yost and Sons", "catchPhrase": "Switchable contextually-based project", "bs": "aggregate real-time technologies" } }, { "id": 10, "name": "Clementina DuBuque", "username": "Moriah.Stanton", "email": "Rey.Padberg@karina.biz", "address": { "street": "Kattie Turnpike", "suite": "Suite 198", "city": "Lebsackbury", "zipcode": "31428-2261", "geo": { "lat": "-38.2386", "lng": "57.2232" } }, "phone": "024-648-3804", "website": "ambrose.net", "company": { "name": "Hoeger LLC", "catchPhrase": "Centralized empowering task-force", "bs": "target end-to-end models" } } ] [] []
As per the placeholder API, we receive the list of users. In this file, we log both request and response. You can change the format as per your requirement.
Conclusion
I hope you have enjoyed this tutorial, Today we’ve explored “Laravel Logging Guzzle Request in File”. You can use this knowledge in your projects. If you have any query please feel free to add in the comment area 😉
Keep Learning, Stay Safe 🙂
Hope you like:
Log All SQL Queries in Laravel
Handle Content Scraping with Pagination in Laravel
How to Handle Logging in Laravel
How to Handle Content Scraping in Laravel
If you like our content, please consider buying us a coffee.
Thank you for your support!
Buy Me a Coffee