farayaz/laravel-spy
Zero-config Laravel package to spy on outgoing HTTP calls. Automatically logs Laravel Http facade and Guzzle requests with URL, method, headers, payload, response/status, and duration. Includes configurable logging and obfuscation for sensitive data.
Installation:
composer require farayaz/laravel-spy
The package auto-discovers and requires no manual bootstrapping.
Publish Configuration (optional, uses defaults otherwise):
php artisan vendor:publish --provider="Farayaz\LaravelSpy\LaravelSpyServiceProvider"
Run Migrations:
php artisan migrate
Creates the http_logs table for storing request/response data.
Enable Logging (via .env):
SPY_ENABLED=true
First Use Case:
Any HTTP request via Laravel’s Http facade or Guzzle will now auto-log. Example:
// In a controller or job
$response = Http::get('https://api.example.com/data');
The request details (URL, method, headers, body, response, duration) are stored in http_logs.
Http:: facade or container-bound Guzzle clients).// Auto-logged (no extra code needed)
Http::post('https://stripe.com/charges', ['amount' => 1000]);
new \GuzzleHttp\Client()) or non-Laravel HTTP clients.SpyMiddleware manually.use Farayaz\LaravelSpy\Middleware\SpyMiddleware;
$client = new \GuzzleHttp\Client([
'middleware' => [
new SpyMiddleware(),
],
]);
SPY_EXCLUDE_URLS in .env:
SPY_EXCLUDE_URLS=*.internal-api.com,/health
'excluded_urls' => [
'*.analytics.example.com',
fn(string $url) => str_contains($url, 'webhook'),
],
config/spy.php:
'obfuscate' => [
'headers' => ['authorization', 'x-api-key'],
'body' => ['password', 'secret', 'token'],
'response' => ['card_*', 'ssn'],
],
credit_card_.*)..env:
SPY_DASHBOARD_ENABLED=true
SPY_DASHBOARD_MIDDLEWARE=auth
/spy (protected by the middleware specified).php artisan spy:clean
app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->command('spy:clean')->daily();
}
// Dispatch a job to log asynchronously
Spy::log($request, $response); // Custom event or queue
spy:log queue job (if extended).$this->assertDatabaseHas('http_logs', [
'url' => 'https://api.example.com/data',
]);
use Farayaz\LaravelSpy\Facades\Spy;
Spy::shouldReceive('log')->once();
Http::fake();
Http::get('https://example.com');
SPY_FIELD_MAX_LENGTH to truncate large responses:
SPY_FIELD_MAX_LENGTH=5000
SPY_EXCLUDE_URLS=*.webhook.example.com
// In a service provider
Spy::extend(function ($app) {
return new class {
public function log($request, $response) {
Log::channel('spy')->info('HTTP Spy', [
'request' => $request->toArray(),
'response' => $response->toArray(),
]);
}
};
});
new \GuzzleHttp\Client()) require manual middleware attachment.$client = app(\GuzzleHttp\Client::class);
SPY_FIELD_MAX_LENGTH to truncate fields:
SPY_FIELD_MAX_LENGTH=2048
SPY_EXCLUDE_URLS=*.example.com/upload
SpyMiddleware, logs may show incorrect headers/body.SpyMiddleware runs first:
$client->getConfig('middleware')->unshift(
new SpyMiddleware()
);
Spy::queueLog($request, $response); // Hypothetical queue method
http_logs table by date.SPY_DASHBOARD_MIDDLEWARE=auth:admin
php artisan tinker
>>> \Farayaz\LaravelSpy\Facades\Spy::logs()->count();
php artisan tinker
>>> \Farayaz\LaravelSpy\Models\HttpLog::latest()->first()->toArray();
.env for verbose output:
SPY_DEBUG=true
tail -f storage/logs/laravel.log | grep spy
Class not found: Ensure the package is auto-discovered or manually register the service provider.composer dump-autoload if the http_logs table isn’t created.HttpLog model to add custom fields:
// app/Models/HttpLog.php
protected $casts = [
'custom_field' => 'json',
];
Schema::table('http_logs', function (
How can I help you explore Laravel packages today?