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

Log Subscriber Laravel Package

guzzlehttp/log-subscriber

Guzzle log subscriber that records HTTP requests and responses via any PSR-3 logger. It helps debug API integrations by logging request method and URL, response status, and other message details, making it easy to inspect traffic and monitor client behavior during development or production troublesh...

View on GitHub
Deep Wiki
Context7

Getting Started

guzzlehttp/log-subscriber is a Guzzle 4/5 event subscriber that attaches to a client's emitter and writes one line per request (and optionally per response) to any PSR-3 logger. Before you install it, confirm which Guzzle your app actually has — Laravel ships Guzzle 6/7 (guzzlehttp/guzzle), where this package is not used. Run composer show guzzlehttp/guzzle first.

If you are genuinely on Guzzle 4/5 (legacy app, pinned dependency), install and wire it up in a service provider or a dedicated client factory:

composer require guzzlehttp/log-subscriber
use GuzzleHttp\Client;
use GuzzleHttp\Subscriber\Log\LogSubscriber;

$client = new Client(['base_uri' => 'https://api.example.com/']);
$client->getEmitter()->attach(new LogSubscriber(app('Psr\Log\LoggerInterface')));

The LoggerInterface resolves to Laravel's logger, so entries land in storage/logs/laravel.log using your configured channel and formatter.

Where to look first: the LogSubscriber constructor (it accepts an optional message formatter), then drop a Log::debug()-level call around your first outbound API request in tinker and watch the log file. That single round trip tells you whether the package is even connected, and what the default message format looks like.

The modern equivalent (Guzzle 6/7, i.e. almost every Laravel app): this package is archived and superseded by middleware bundled with Guzzle itself:

use GuzzleHttp\Middleware;
use GuzzleHttp\MessageFormatter;

$stack = HandlerStack::create();
$stack->push(Middleware::log(
    app('Psr\Log\LoggerInterface'),
    new MessageFormatter(MessageFormatter::DEBUG)
));

$client = new Client(['handler' => $stack]);

If you are writing new code, start here; read the rest of this assessment as the legacy path plus the shared concepts (formatting, redaction, levels) that carry over unchanged.

Implementation Patterns

Per-client middleware, not global mutation. Build clients in a small factory or service provider method and attach logging there. Avoid attaching to a shared singleton client and then trying to detach — the emitter is append-only in practice, and you will end up double-logging.

// app/Http/Clients/ApiClient.php
public function __construct()
{
    $this->client = new Client([
        'base_uri' => config('services.api.base_url'),
        'timeout'  => 10,
    ]);

    if (config('services.api.log_http')) {
        $this->client->getEmitter()->attach(
            new LogSubscriber(app('Psr\Log\LoggerInterface'))
        );
    }
}

Gate it behind config/environment. Logging every request in production is noisy and expensive. A dedicated channel plus a config flag gives you an on/off switch without touching code:

// config/logging.php
'api' => [
    'driver' => 'daily',
    'path'   => storage_path('logs/api.log'),
    'level'  => 'debug',
    'days'   => 7,
],

Custom message formatting. The default formatter prints method, URL, status, and timing. Real day-to-day debugging usually needs the correlation ID and the response body for failures. Register a formatter that closes over the current request ID:

$formatter = new MessageFormat(...); // or a Closure-based formatter

$subscriber = new LogSubscriber($logger, $formatter);

In modern Guzzle, MessageFormatter takes a format string with placeholders ({method}, {url}, {code}, {req_body}, {res_body}, {req_headers}, {res_headers}, {error}) — this is the cleanest way to control verbosity, and it is the part worth learning even if you never use the archived package.

Log level switching. In Guzzle 6/7, Middleware::log() takes a level as its third argument; use 'debug' for local work and 'warning' in production so only errors and 5xx responses surface. Use a formatter that emits a short line for 2xx and a full dump for 4xx/5xx by pushing two middleware instances with different levels and a shared formatter — or, more simply, one formatter that branches on {code}.

Integration with Laravel's HTTP faking and tests. Never let a real subscriber hit the network in tests. Http::fake() handles this for the Laravel HTTP client; for raw Guzzle, swap the handler for MockHandler and attach the logger to verify that you log, not just that the call succeeded:

$mock = new MockHandler([new Response(500)]);
$stack = HandlerStack::create($mock);
$stack->push(Middleware::log($logger, new MessageFormatter('{method} {url} -> {code}')));

Then assert against the logger spy.

Correlation IDs. Push a middleware that injects X-Request-Id and have the formatter include it. This is the single highest-value extension point: logs become greppable across services and job retries.

Queue/console context. Wrap the client call so the logger context includes job and attempt. Guzzle's log middleware doesn't know about Laravel's context, so add it via Log::withContext() in the job or a middleware that reads config('app.name') / request ID.

Gotchas and Tips

  • Archived and Guzzle-4/5 only. Installing it on a Guzzle 6/7 stack will either fail to autoload the expected classes or silently do nothing, because getEmitter() no longer exists. If you see "Call to undefined method GuzzleHttp\Client::getEmitter()", you are on the wrong package — switch to Middleware::log().
  • Bodies are streams. Logging {req_body}/{res_body} consumes the stream. The middleware should rewind it, but custom formatter code frequently reads getBody()->getContents() and leaves the position at EOF, producing empty responses downstream. Always rewind() (or (string) $stream, which casts without moving the pointer in most cases) after inspecting.
  • Secrets leak. Authorization headers, API keys, cookies, and PII in bodies will land in laravel.log verbatim. Before enabling in any shared environment, log headers through a redaction filter and prefer {url} + {code} over full headers.
  • Unbounded payloads. A 5 MB JSON body formatted into the log will bloat the file and slow the request. Truncate in the formatter; log bodies only for failures.
  • Double logging. If you attach a subscriber at the client and you use Laravel's Http facade, you'll get two lines for one call — the facade has its own logging via Http::withMiddleware(). Pick one layer.
  • Level vs. channel confusion. A PSR-3 logger pointed at a channel whose level is warning will silently drop your debug entries. You will spend an hour blaming the package. Check config/logging.php first.
  • Error events. In Guzzle 4/5, failed requests don't always hit the "complete" event; make sure your formatter handles the exception case ({error} placeholder) or connection failures disappear from the log entirely.
  • Extension point: the message formatter is the only sane customization surface. Subclassing LogSubscriber to tweak behavior couples you to the archived API; a formatter closure/string is portable to Middleware::log() when you migrate.
  • Migration tip: wrap the logging setup behind your own LoggingClientFactory so that switching from the subscriber to Guzzle's middleware is a one-file change rather than a grep across every service class.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle