juliendidier/buzz-profiler-bundle
juliendidier/buzz-profiler-bundle adds a dedicated Symfony web profiler panel for the Buzz HTTP client, so you can inspect every outgoing request, response, headers, and timing directly in the toolbar and profiler. A lightweight way to debug HTTP calls without leaving your Symfony app.
First, the reality check. juliendidier/buzz-profiler-bundle is a Symfony 2 bundle, not a Laravel package. It registers itself through AppKernel, hooks Symfony's data_collector service tag, and renders a Twig panel in the Web Debug Toolbar via assets:install. There is no service provider, no facade, and no Laravel extension point it can attach to. If your composer.json has laravel/framework in it, you will not get a toolbar icon by installing this. What you can take from it is the pattern: a profiler panel that records every outbound HTTP call (method, URL, status, headers, body, duration). Laravel already ships that pattern natively.
If you are actually on legacy Symfony 2.x with Buzz:
composer require juliendidier/buzz-profiler-bundle
php app/console assets:install --symlink
php app/console cache:clear --env=dev
// app/AppKernel.php
if (in_array($this->getEnvironment(), ['dev', 'test'])) {
$bundles[] = new Juliendidier\BuzzProfilerBundle\BuzzProfilerBundle();
}
Then load any page that makes an outbound Buzz call and click the toolbar icon to see the request list grouped by client instance.
If you are on Laravel — look here first, in this order:
composer require laravel/telescope). Open /telescope → Requests tab. The ClientWatcher records URL, method, status, headers, payload, and duration for anything sent through Illuminate\Support\Facades\Http. This is a superset of what the bundle does.itsgoingd/clockwork) if you prefer an in-browser timeline over a separate Telescope UI.Http::recorded() in tests if all you wanted was a debug-time view of what went out.First use case to validate the setup: make one slow third-party call (a payment provider sandbox, an enrichment API) from a controller, hit the page, and confirm you can see duration and status without adding a Log::info. If that works, the tooling is wired correctly.
The bundle's core is "observe every Buzz call without touching call sites." In Laravel you get the same with HTTP client events, which fire for every Http:: call globally:
// app/Providers/HttpObservabilityServiceProvider.php
use Illuminate\Http\Client\Events\{RequestSending, ResponseReceived, ConnectionFailed};
use Illuminate\Support\Facades\Event;
public function boot(): void
{
Event::listen(ResponseReceived::class, function (ResponseReceived $e) {
$response = $e->response;
$request = $response->transferStats?->getRequest() ?? $response->toPsrResponse()->getRequest();
Log::channel('http')->debug('outbound', [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'status' => $response->status(),
'ms' => round($response->transferStats?->getTransferTime() * 1000),
'client' => $e->request->attributes->get('client_name'), // see Pattern 4
]);
});
Event::listen(ConnectionFailed::class, fn ($e) => Log::channel('http')->warning('outbound failed', [
'uri' => $e->request->url(),
'err' => $e->exception->getMessage(),
]));
}
This is the day-to-day lever: one listener, zero call-site edits, every Http:: call visible.
The bundle groups calls by Buzz client instance so you can compare, e.g., the billing client against the CRM client. Reproduce it with named clients and tag the request:
// config/services.php or a dedicated config/http.php
'clients' => [
'billing' => ['base_uri' => env('BILLING_URL'), 'timeout' => 5],
'crm' => ['base_uri' => env('CRM_URL'), 'timeout' => 2],
],
Http::globalRequestMiddleware(function ($request) {
// attribute is set by the wrapper below; default when missing
return $request;
});
function billing(): PendingRequest {
return Http::baseUrl(config('clients.billing.base_uri'))
->timeout(config('clients.billing.timeout'))
->withOptions(['client_name' => 'billing']); // surfaced as attribute in listener
}
Then your profiler/log view can pivot duration by client_name — which is exactly the "is it us, the vendor, or the network?" question the bundle exists to answer.
The bundle shows duration; the day-to-day win is turning that into a threshold you notice:
Http::globalMiddleware(function ($handler) {
return function ($request, $options) use ($handler) {
$start = hrtime(true);
return $handler($request, $options)->then(function ($response) use ($start, $request) {
$ms = (hrtime(true) - $start) / 1e6;
if ($ms > 1000) {
Log::warning('slow outbound', ['uri' => (string) $request->getUri(), 'ms' => (int) $ms]);
}
return $response;
});
};
});
Register this in a service provider's boot(). Keep the threshold configurable and off in production (see Gotchas).
The biggest operational risk with any HTTP profiler is bearer tokens landing in a profiler cache. In Laravel, redact centrally:
// TelescopeServiceProvider::register() — Telescope 4.x+
Telescope::hideRequestHeaders(['authorization', 'cookie', 'x-api-key']);
Telescope::hideRequestParameters(['password', 'token', 'secret', 'card_number']);
For your own listener, whitelist rather than blacklist:
'headers' => collect($request->getHeaders())
->only(['accept', 'content-type', 'user-agent'])
->map(fn ($v) => $v[0] ?? null)
->all(),
Http::fake() + Http::recorded() is the closest thing Laravel has to "replay the profiler for this request" and it's better than the toolbar for CI:
Http::fake(['api.vendor.test/*' => Http::response(['ok' => true], 200)]);
$this->post('/checkout')->assertOk();
Http::assertSent(fn ($req) => $req->url() === 'https://api.vendor.test/charge'
&& $req->hasHeader('Idempotency-Key'));
If your team insists on a bespoke panel, the port is: DataCollector → a class implementing Laravel\Telescope\Watchers\Watcher with register($telescope) hooking the same events as Pattern 1, plus a format() that returns the entry array Telescope renders. Budget a day; don't port the Twig template, Telescope's Vue UI already renders it.
HttpObservabilityServiceProvider) so it can be conditionally registered by environment.config('app.debug') and an explicit env flag (HTTP_PROFILING=true), because APP_DEBUG gets flipped on in staging more often than anyone admits.Http::), use Guzzle history middleware — $stack->push(Middleware::history($container)) — and render $container into Debugbar's timeline. Same idea, different client.Installation / bootstrap
assets:install yields a toolbar panel that throws, which degrades the whole debug toolbar — not just this panel. Always run assets:install after composer update.cache:clear --env=dev) after enabling; Symfony 2 caches the container aggressively and the data_collector tag won't be picked up otherwise.Environment gating
in_array($env, ['dev','test']) block in AppKernel, not unconditionally. The Symfony profiler writes per-request files to var/cache/<env>/profiler.if (! config('app.debug')) return; at the top of boot(). Under load, HTTP capture serializes payloads to storage per request — large uploads or streamed responses cause memory spikes and disk growth.Security
var/cache/*/profiler and are renderable in the UI. Verify redaction before enabling anywhere shared, and treat the profiler cache directory as a sensitive path in your .gitignore/CI artifact rules.Telescope::hideRequestHeaders(...) is actually applied — it's easy to configure redaction and then add a second collector (Debugbar, Clockwork) that bypasses it.Dependency / compatibility
kriswallsmith/buzz) predates PSR-18/PSR-17 and does not interoperate with modern clients. Assume it cannot coexist with PHP 8 + Symfony 4+ in the same dependency graph. Do not attempt an in-place upgrade — swap the client, then remove the bundle.Debugging tips
data_collector, (2) is the Buzz client actually the instance the listener subscribed to (not a manually new'd client), (3) is the profiler enabled for that environment (framework.profiler in config_dev.yml).dump() the raw collector data via app.request.attributes->get('_profiler')-style inspection before blaming the template; the rendering layer fails more quietly than the collector.Http:: calls don't appear in Telescope, check config/telescope.php → watchers.client.enabled and the ignore list — an over-broad ignore pattern silently swallows entries.Extension points to borrow from the bundle
data_collector tag + a Twig template is the whole architecture; on Laravel the analogues are a Telescope\Watchers\Watcher plus the built-in Vue entry renderer.grep through a log file.> 10 requests/response gives you the same tripwire.Bottom line for a Laravel developer: don't install it, don't port the templates. Copy the two ideas that matter — central capture of outbound calls with duration and status and grouping by logical client — and implement them with Telescope's ClientWatcher plus Http events. You get the same debugging visibility, on a maintained dependency, in an afternoon.
How can I help you explore Laravel packages today?