andriichuk / laravel-http-client-logger
Configurable request/response logger for Laravel HTTP client with sanitization and status filters
Package info
github.com/andriichuk/laravel-http-client-logger
pkg:composer/andriichuk/laravel-http-client-logger
Fund package maintenance!
Requires
- php: ^8.3
- guzzlehttp/guzzle: ^7.0
- illuminate/http: ^11.0||^12.0
- illuminate/support: ^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
README
A super simple, configurable logger for Laravel’s HTTP client (outgoing requests). Ideal for APIs and third-party integrations.
- Sanitization — Mask sensitive fields and headers (e.g. password, authorization).
- Filters — Limit by response status (2xx, 4xx, 5xx, etc.).
- Headers — Choose which request/response headers to include in logs. Use
['*']for all; when the arrays are empty, no header keys are added to the log at all. - Optional response body — Include decoded, sanitized response body (JSON); non-JSON can be truncated or skipped.
- Request files (multipart) — When sending files via
attach(), log metadata (name, original_name, size, mime_type, extension) in context asuploaded_files; no file contents are logged. Aligns with laravel-http-logger.
Installation
Requirements: PHP 8.2+ and Laravel 10.x, 11.x or 12.x.
Install the package via Composer:
composer require andriichuk/laravel-http-client-logger
Publish the config file:
php artisan vendor:publish --tag="http-client-logger-config"
(Optional) Add a dedicated log channel for HTTP client logs in config/logging.php (e.g. a separate file or stack). If you skip this, the package uses your default log channel.
'channels' => [ // ... 'http_client' => [ 'driver' => 'daily', 'path' => storage_path('logs/http_client.log'), 'level' => 'debug', ], ],
Important — when is logging on? If
LOG_HTTP_CLIENT_REQUESTSis not set in.env, the logger followsAPP_DEBUG: it is enabled whenAPP_DEBUG=true(e.g. local) and disabled whenAPP_DEBUG=false(e.g. production). To force it on or off, setLOG_HTTP_CLIENT_REQUESTS=trueorLOG_HTTP_CLIENT_REQUESTS=falsein your.env, or set'enabled'inconfig/http-client-logger.php. The log channel can be overridden withHTTP_CLIENT_LOG_CHANNEL(e.g.HTTP_CLIENT_LOG_CHANNEL=http_clientto use the channel above).
Configuration
After publishing, configure config/http-client-logger.php as needed.
| Key | Description | Default |
|---|---|---|
enabled |
Master switch for HTTP client logging. When LOG_HTTP_CLIENT_REQUESTS is unset, falls back to APP_DEBUG. When disabled, the log macro still exists but no entries are written. |
env('LOG_HTTP_CLIENT_REQUESTS', APP_DEBUG) |
channel |
Log channel name (must exist in config/logging.php). |
HTTP_CLIENT_LOG_CHANNEL or LOG_CHANNEL or 'stack' |
report |
Which response status categories to log: info (1xx), success (2xx), redirect (3xx), client_error (4xx), server_error (5xx). Each key is a boolean. |
info/success → false; redirect/client_error/server_error → true |
log_level_by_status |
Map each status category to a PSR log level (debug, info, notice, warning, error, etc.). 5xx → error and 4xx → warning by default for easier filtering. |
client_error → warning; server_error → error; others → info |
include_response |
Include response body in log context. | true |
include_non_json_response |
When include_response is true, include non-JSON bodies (HTML, text, etc.) in the log (truncated). Set to true to include them; default logs as '[skipped]'. |
false |
include_request_headers |
Request header names (lowercase) to include. Use ['*'] for all. When empty, no request headers are added to the log. |
['*'] |
include_response_headers |
Response header names (lowercase) to include. Use ['*'] for all. When empty, no response headers are added to the log. |
['*'] |
include_uploaded_files_metadata |
When true, multipart requests that send files (e.g. via attach()) include file metadata in log context as uploaded_files: name, original_name, size, mime_type, extension, error. No file contents are logged. |
true |
sensitive_fields |
Request/response body keys to replace with ***. |
['token', 'refresh_token', 'password', …] |
sensitive_headers |
Header names (lowercase) to replace with ***. |
['authorization', 'cookie'] |
max_string_value_length |
Max length for string values in bodies (and non-JSON response body) before truncation. Use null to disable truncation. |
100 |
message_prefix |
Prefix for the log message. | '[HttpClientLogger] ' |
Response body logging: JSON responses are decoded and sanitized; max_string_value_length applies to each string value. Non-JSON responses are logged as a truncated string or '[skipped]'. The log level follows response status by default (5xx → error, 4xx → warning, 1xx/2xx/3xx → info); configure via log_level_by_status.
Usage
Use the log macro on the Laravel HTTP client to log that request and its response (or failure).
Basic:
use Illuminate\Support\Facades\Http; Http::log()->get('https://api.example.com/users'); Http::log()->post('https://api.example.com/orders', ['item' => 'widget']);
With context (e.g. name in the log message):
Http::log(['name' => 'Stripe'])->post('https://api.stripe.com/v1/charges', $payload);
Using the name macro:
Use the name macro to add a name key to the logging context and include it in the log message. Handy for chaining and identifying which integration a request belongs to.
Http::name('Stripe')->log()->get('https://api.stripe.com/v1/balance');
The name appears in the log message right after the prefix (e.g. [HttpClientLogger] Stripe GET https://api.stripe.com/v1/balance).
Sending files (multipart):
When you send files with attach(), the logger can include file metadata in the log context as uploaded_files (name, original_name, size, mime_type, extension). No file contents are logged. Enable or disable via include_uploaded_files_metadata in config.
Http::log()->attach('document', $fileContents, 'report.pdf')->post('https://api.example.com/upload'); // or with Laravel's UploadedFile: Http::log()->attach('avatar', $request->file('avatar'))->post('https://api.example.com/profile/photo');
When enabled is false in config, the macro is still available but the middleware does not write any log entries.
Callbacks
You can register one or more callbacks that run when a request is logged (after the same checks that allow logging: enabled, and for success responses, the status filter). Use this to push metrics, notify, or run custom logic whenever an outgoing request is logged.
Callback signature: (RequestInterface $request, ?ResponseInterface $response, float $executionTimeMs): void
- On success (in
logSuccess),$responseis the PSR-7 response. - On exception (in
logException),$responseisnull.
Register callbacks in your AppServiceProvider::boot() method using the HttpClientLogger facade:
use Andriichuk\HttpClientLogger\HttpClientLogger; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; public function boot(): void { HttpClientLogger::addCallback(function (RequestInterface $request, ?ResponseInterface $response, float $executionTimeMs): void { // $response is null when the request failed (exception path) // Runs only when logging is enabled and (for success) status is reported }); }
Callbacks are invoked before the package writes to the log context. If a callback throws, the exception is not caught by the package and bubbles up to the caller.
Example log output
Successful API call (200): INFO level, with name and sanitized response.
[2026-03-14 10:15:22] local.INFO: [HttpClientLogger] Stripe GET https://api.stripe.com/v1/balance {"response_status_code":200,"request_headers":{"content-type":["application/json"],"authorization":"***"},"response_headers":{"content-type":["application/json"]},"request_body":[],"response_body":{"available":[{"amount":1000,"currency":"usd"}],"token":"***"},"execution_time_ms":142}
Client error (422): WARNING level, authorization masked, JSON response with validation errors.
[2026-03-14 10:16:01] local.WARNING: [HttpClientLogger] POST https://api.example.com/orders {"response_status_code":422,"request_headers":{"content-type":["application/json"],"authorization":"***"},"response_headers":[],"request_body":{"item":"widget","quantity":-1},"response_body":{"message":"The quantity must be at least 1.","errors":{"quantity":["The quantity must be at least 1."]}},"execution_time_ms":89}
Message (no name): [HttpClientLogger] GET https://api.example.com/users
Multipart with file: uploaded_files in context (metadata only; no file contents).
[2026-03-14 10:20:00] local.INFO: [HttpClientLogger] POST https://api.example.com/upload {"response_status_code":200,"request_headers":{...},"response_headers":{},"request_body":"...","response_body":"...","execution_time_ms":45,"uploaded_files":[{"name":"document","original_name":"report.pdf","size":null,"mime_type":null,"extension":"pdf","error":0}]}
Testing
Run the test suite with Pest:
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
License
The MIT License (MIT). Please see License File for more information.