Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Pulse Guzzle Recorder Laravel Package

muhammadhuzaifa/laravel-pulse-guzzle-recorder

Laravel Pulse Guzzle Recorder adds a Pulse card and recorder that captures outgoing Guzzle HTTP requests—URL, method, status code, duration, and errors—so you can monitor third-party API traffic and slow dependencies right inside your Laravel Pulse dashboard. Easy to install and configure.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish config

composer require muhammadhuzaifa/laravel-pulse-guzzle-recorder
php artisan vendor:publish --tag="laravel-pulse-guzzle-recorder-config"

Read the published config/laravel-pulse-guzzle-recorder.php first — the two keys that matter on day one are the enable flag and the slow threshold (milliseconds). Everything else in this package is a wrapper around those two values plus a Guzzle middleware.

Confirm Pulse itself is working before blaming the package. The recorder writes into Pulse's SlowOutgoingRequests card, which reads from the pulse_entries / pulse_aggregates tables:

php artisan migrate          # if Pulse migrations haven't run
php artisan pulse:check      # or your configured ingest mechanism

Then make sure the card is actually on your dashboard in config/pulse.php:

'cards' => [
    // ...
    \Laravel\Pulse\Cards\SlowOutgoingRequests::class => [
        'threshold' => 1000, // card-side display threshold, separate from the recorder threshold
    ],
],

First use case. Pick one slow third-party integration you already suspect — a payments API, a CRM sync, a webhook dispatcher. Wrap that client with the recorder's middleware, set the threshold to something aggressive (e.g. 200 ms) in local, hit the endpoint, and watch the card populate. That single loop teaches you the entire package: middleware registration → threshold comparison → Pulse entry write → card aggregation. Once it works for one client, you scale it out.

Sanity check first. Before wiring anything, verify the middleware class name the package registered by reading the service provider. This package is small and the README is the only documentation — treat the provider as the source of truth for what's actually being pushed onto the handler stack and under what config key.

Implementation Patterns

Pattern 1 — one shared instrumented client (recommended default).

Don't sprinkle middleware across ad-hoc new Client() calls. Bind a single configured client in a service provider and resolve it everywhere:

// AppServiceProvider::register()
$this->app->singleton(\GuzzleHttp\Client::class, function () {
    $stack = \GuzzleHttp\HandlerStack::create();

    // recorder middleware pushed here — see the package provider for the exact handle
    $stack->push(app(\MuhammadHuzaifa\PulseGuzzleRecorder\Middleware\RecordSlowRequest::class)->middleware());

    return new \GuzzleHttp\Client(['handler' => $stack]);
});

This gives you one place to tune the threshold, one place to add exclusions, and it means every SDK that respects container resolution inherits instrumentation for free.

Pattern 2 — targeted middleware on a single client.

For a client you only use in one service (a vendor SDK wrapper, a webhook sender), build the stack inline and keep the blast radius small:

$stack = HandlerStack::create();
$stack->push($recorder->middleware());
$client = new Client(['handler' => $stack, 'base_uri' => 'https://api.vendor.test']);

Prefer this when you want to instrument noisy-but-important traffic without flooding the dashboard with every Guzzle call in the app.

Pattern 3 — the Laravel HTTP client bridge.

Http:: uses Guzzle underneath, but it constructs its own handler stack, so the middleware will not apply automatically. Bridge it explicitly:

Http::globalOptions([
    'handler' => $instrumentedStack,
]);

or per-request:

Http::withOptions(['handler' => $stack])->get('https://api.vendor.test/orders');

The globalOptions route is the cleanest way to get blanket coverage of facade-based calls; the per-request route is what you use when you only want a handful of endpoints recorded.

Pattern 4 — environment-aware thresholds.

Slow in local and slow in production are different numbers. Drive the threshold from config rather than hardcoding:

// config/laravel-pulse-guzzle-recorder.php
'threshold' => env('PULSE_GUZZLE_SLOW_MS', 1000),

Set it low (100–200 ms) in local/staging so you actually see entries, and high (1000–3000 ms) in production so the card stays signal-only. A recorder that fires on everything is worse than no recorder.

Pattern 5 — pair with Pulse sampling.

Pulse already has Pulse::sample() gating. If your app makes thousands of outbound calls per minute, let Pulse's sampling rate do the volume reduction and keep the package threshold conservative. Two independent throttles (sampling + threshold) is the right shape; tuning only one usually means the card is either empty or unreadable.

Pattern 6 — tag your requests for dashboard segmentation.

Where the package supports attaching context (check the middleware signature — most Pulse recorders allow passing through the Guzzle options array), set a stable identifier per integration so the card groups cleanly:

$client->request('GET', '/orders', [
    'pulse_key' => 'vendor-api:orders', // or whatever the package exposes
]);

If it doesn't support custom keys, normalize at the call site instead — keep base URIs and path shapes consistent so Pulse's URL grouping doesn't fragment one integration into forty rows.

Gotchas and Tips

Http::fake() bypasses everything. This is the number one source of "the recorder doesn't work" reports. The fake swaps in a stub handler before Guzzle runs, so your middleware never executes and nothing lands in Pulse. Your test suite will look green and your dashboard will look empty. Never assert recorder behaviour through Http::fake(); write one integration test against a real (local) endpoint or a Guzzle MockHandler pushed underneath the recorder middleware.

Config cache will bite you. After changing the threshold, run php artisan config:clear (or config:cache in deploy). A threshold that "isn't taking effect" is almost always a stale cached config, not a package bug.

Fast requests are invisible by design. The recorder only writes entries above the threshold. When debugging, temporarily drop the threshold to 0 and confirm entries appear at all — that separates "middleware isn't registered" from "nothing was slow enough."

Watch the two thresholds. There's a recorder-side threshold (what gets written to pulse_entries) and the card-side threshold in config/pulse.php (what gets displayed). If the recorder writes at 500 ms but the card filters at 1000 ms, you'll have entries in the database and an empty card. Keep recorder ≤ card threshold, always.

URL grouping is string-based. Pulse's Slow Outgoing Requests card aggregates on the request URI. Query strings with IDs, timestamps, or tokens will shred your aggregation into one row per call. Either strip volatile query params before the request reaches the recorder, or accept the noise and filter on the dashboard. This is the most common "why does my card look useless" complaint.

Middleware ordering matters. Push the recorder closest to the handler (i.e. last in the stack) if you also use retry, logging, or auth middleware — otherwise you'll record the pre-retry attempt, the auth-refresh attempt, and the actual call as three separate slow requests. If you want one entry per logical call, push it above retry instead. Decide deliberately; both are defensible.

Async and promises can double-count or miss. With requestAsync + wait(), the middleware's fulfil/reject callbacks fire on resolution. If you fire a batch of promises and only inspect some, you can end up with entries for requests whose result you discarded. If your app uses concurrent pools, test the recording behaviour under Pool explicitly before trusting the numbers.

Streaming responses can skew timing. A stream => true request resolves the promise as soon as headers arrive; the recorder will report the header latency, not the body download. For large downloads this makes the dashboard lie in the optimistic direction. Instrument streaming endpoints separately if they matter.

Exceptions must still propagate. Correct Guzzle middleware returns $handler($request, $options) and attaches then callbacks for the side effect only. If the package's middleware ever swallows or rewraps an exception, your error handling changes silently. Verify with a client pointed at a known-failing endpoint that the original exception class still surfaces.

Empty dashboard? Check the ingest. The recorder writes to pulse_entries, and the card reads from pulse_aggregates populated by pulse:check / pulse:work. If entries exist but the card is blank, the problem is downstream of this package — check your queue worker and the pulse_work / ingest process, not the middleware.

Extension points are thin, so wrap it, don't fork it. At 17 stars with zero dependents, expect minimal API surface and infrequent maintenance. Keep your instrumentation in a thin application-level wrapper (your own InstrumentedClientFactory) so an upstream change or abandonment costs you one file, not a scattered grep across the codebase. And pin the version in composer.json — a small package with a 2026 release date and no dependents is not something you want floating.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource