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

Telescope Guzzle Watcher Laravel Package

muhammadhuzaifa/telescope-guzzle-watcher

Adds a Guzzle watcher to Laravel Telescope, recording outgoing HTTP client requests in the Telescope dashboard. Helps debug third-party API calls, headers, responses, and timing alongside your other Telescope entries.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package alongside Telescope and Guzzle:

composer require muhammadhuzaifa/telescope-guzzle-watcher

1. Register the watcher in config/telescope.php. This is the first place to look — if the watcher isn't in the watchers array, nothing will ever be recorded and you'll waste an hour debugging client config:

'watchers' => [
    // ... existing watchers

    MuhammadHuzaifa\TelescopeGuzzleWatcher\Watchers\GuzzleWatcher::class => [
        'enabled' => env('TELESCOPE_GUZZLE_WATCHER', ! app()->isProduction()),
    ],
],

Add TELESCOPE_GUZZLE_WATCHER=true to .env.local / .env.staging and leave it off in production. Clear config cache after editing (php artisan config:clear).

2. Attach the watcher to the Guzzle client. The package hooks into Guzzle's on_stats request option, so it is opt-in per client (or per client factory). The README shows the exact callable/FQCN for your version; the shape is:

use GuzzleHttp\Client;
use MuhammadHuzaifa\TelescopeGuzzleWatcher\Watchers\GuzzleWatcher;

$client = new Client([
    'base_uri' => 'https://api.example.com',
    'on_stats' => [GuzzleWatcher::class, 'recordStats'], // exact signature per README
]);

If your version ships a HandlerStack middleware, push it instead so it applies to every request on that stack:

$stack = HandlerStack::create();
$stack->push(GuzzleWatcher::middleware(), 'telescope');
$client = new Client(['handler' => $stack]);

First use case: a Stripe/AWS/Shopify/Elasticsearch SDK call returns a 500 or times out. Those SDKs build their own Guzzle client internally, so they never appear in Telescope's HTTP Client tab — Laravel's Http facade watcher only covers Illuminate\Support\Facades\Http. This package closes that gap. Open Telescope → HTTP Client, filter by time, and you'll see method, URI, status, headers, payload, and duration just like Http:: calls.

Implementation Patterns

Central client factory. Don't sprinkle on_stats across the codebase. Put every outbound Guzzle client behind one factory or container binding so the watcher is attached exactly once:

// app/Http/Integrations/ApiClientFactory.php
public function make(array $config = []): Client
{
    return new Client(array_merge([
        'timeout' => 10,
        'on_stats' => [GuzzleWatcher::class, 'recordStats'],
    ], $config));
}

Wrap SDKs that expose a handler. AWS SDK, Elasticsearch, and many others accept a handler/http_handler option — inject a HandlerStack with the middleware pushed. This is the cleanest path for vendor SDKs you don't control:

$sdk = new Aws\S3\S3Client([
    'region' => 'eu-west-1',
    'http_handler' => $this->telescopeStack(), // stack with watcher pushed
]);

Chain existing on_stats callbacks. If a client already uses on_stats for metrics or retry logging, don't replace it — wrap it, otherwise you'll silently lose coverage:

$previous = $config['on_stats'] ?? null;

$config['on_stats'] = function (TransferStats $stats) use ($previous) {
    $previous && $previous($stats);
    // watcher call here
};

Tag requests for filtering. Wrap the call in Telescope::tag() so entries carry a domain (stripe, shipping, erp) and you can find them in a busy log:

Telescope::tag(fn () => ['integration' => 'stripe'], fn () => $client->post('/charges', [...]));

The tag applies to everything recorded during the callback, including the Guzzle entry.

Test assertions. In feature tests with Telescope enabled, assert on the telescope_entries table or use Telescope's assertion helpers to prove an integration was actually called — useful for contract tests against mocked handlers:

$this->assertDatabaseHas('telescope_entries', [
    'type' => 'http_client',
    'content' => 'like', '%api.stripe.com%',
]);

Local debugging workflow. Reproduce with TELESCOPE_GUZZLE_WATCHER=true, hit the endpoint, then compare the recorded request against what the vendor docs expect. Because the entry carries request headers and body, it's often faster than adding Log::debug() around the SDK call — and it survives into the failure path since the entry is written from on_stats even when the response is a 4xx/5xx.

Gotchas and Tips

The watcher is opt-in, not automatic. Unlike Telescope's HttpClientWatcher, installing the package does nothing until you attach on_stats/middleware and enable it in config/telescope.php. Both must be true. When nothing appears, check the watcher entry first, then the client config.

on_stats fires from the handler, not the middleware stack. If another middleware short-circuits (cache hit, mock handler, circuit breaker returning a synthetic response), on_stats never runs and nothing is recorded. If you're using mocks in tests, expect zero entries — that's a useful signal, not a bug.

Chaining order matters. If two layers both set on_stats (your factory and a caller passing 'on_stats' => ...), the later array merge wins and one watcher is dropped. Centralise client construction, or explicitly compose callbacks.

Header and body leakage. Entries capture Authorization, API keys, and full request bodies. In non-production this is usually fine, but on shared staging environments configure Telescope's hideRequestHeaders / hideRequestParameters (and Telescope::hideRequestParameters()) before someone pastes a live Stripe key into a Slack channel.

Streaming and sink responses. When the SDK streams to disk or returns a non-seekable body, the recorded response payload may be empty or truncated. Status, headers, and timing are still reliable — treat those as the source of truth for streamed calls.

Async/promise clients. Stats are only available once the promise resolves. If you fire-and-forget with ->then() and the request is never awaited (e.g., in a queued job that exits), the entry won't be written. Await or ->wait() explicitly.

Duplicates with the Http facade. If a package uses Http:: internally, Telescope's built-in watcher already records it. Adding this watcher around the same client yields two entries. Don't wrap facade-driven code — this package is specifically for raw Guzzle.

Volume and pruning. SDKs that poll (SQS, health checks, webhooks) can generate thousands of entries quickly and bloat telescope_entries. Use Telescope::filter() to drop noisy URIs, and keep telescope:prune scheduled. Disable the watcher entirely in production via the env flag.

Version matrix. on_stats receives a TransferStats and its API differs between Guzzle 6 and 7. Pin Guzzle 7 and check the package's supported Telescope major versions (4.x/5.x) before upgrading either — a Telescope upgrade that changes the HTTP client entry schema can leave you with entries that render blank in the UI.

Extension point. The watcher ultimately writes through Telescope's IncomingEntry/HttpClientWatcher path, so Telescope::filter() and Telescope::tag() behave exactly as they do for Http:: calls. If you need custom fields, that's the seam to hook — not the Guzzle layer.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package