andriichuk/laravel-http-logger
A Laravel package that logs outgoing HTTP requests and responses to the database, making it easy to inspect, debug, and monitor third-party API calls with configurable logging, pruning, and a built-in dashboard.
Install the package with Composer and publish its single config file:
composer require vendor/http-logger
php artisan vendor:publish --tag=http-logger-config
That gives you config/http-logger.php — this is the only file you need to know about. Everything the package does (routing, status filtering, header/body sanitization, channel, prefix) is driven from there and is env-aware, so skim it top-to-bottom once before changing anything.
Minimum viable setup for a first use case — log only API traffic, only errors, to a dedicated channel:
// config/http-logger.php
'enabled' => env('HTTP_LOGGER_ENABLED', false),
'channel' => env('HTTP_LOGGER_CHANNEL', 'http_logger'),
'prefix' => '[HttpLogger]',
'routes' => ['api/*'],
'statuses' => [400, 401, 403, 404, 422, 429, 500, 502, 503],
// config/logging.php
'channels' => [
'http_logger' => [
'driver' => 'daily',
'path' => storage_path('logs/http-logger.log'),
'days' => 14,
'level' => 'debug',
],
],
HTTP_LOGGER_ENABLED=true
HTTP_LOGGER_CHANNEL=http_logger
The package hooks Laravel's RequestHandled event, so there is nothing to register in a service provider or middleware stack — once enabled and the channel exists, requests matching your route patterns start landing in storage/logs/http-logger.log-YYYY-MM-DD.
First use case to reach for: turning this on in staging with routes => ['api/*'] and statuses => [4*, 5*]-style filtering to get a readable error trail for failing API calls, without drowning in 200s.
Environment-scoped enablement. Treat enabled as the primary switch and keep it false by default in config/http-logger.php. Flip it per environment via HTTP_LOGGER_ENABLED — on in local/staging, off (or errors-only) in production. Do not rely on route/status filters alone as your volume control.
Route patterns as the coarse filter. routes accepts patterns in the same spirit as Route::is(), so api/*, api/v1/orders*, webhooks/* all work. Prefer a small number of explicit patterns over *; the listener runs on every request that matches, so a broad pattern is a broad performance and log-volume cost. Use a separate pattern set per environment if your route surface differs.
Status buckets for signal-to-noise. statuses takes 1xx–5xx classes. Two common profiles:
[400, 401, 403, 404, 409, 422, 429, 500, 502, 503, 504] — the everyday choice.[1, 2, 3, 4, 5] — for a debugging window only. 2xx/3xx logging generates enormous volume fast.Header allowlists plus masking. Set the header allowlist to what you actually need to correlate a request (X-Request-Id, Accept, Content-Type, User-Agent), and make sure the sensitive-header mask list covers Authorization, Cookie, Set-Cookie, X-Api-Key, and any internal auth headers. Masking happens on output, but the safe default is: don't allowlist a header you don't need.
Body sanitization as a config contract. The sensitive-field list and truncation length are the two settings worth codifying in code review. Add your domain's secret-ish fields (password, password_confirmation, token, secret, card_number, cvv, ssn) and pick a truncation length that keeps logs greppable — a few hundred to a couple of thousand characters is usually right. Long truncation defeats the purpose on payload-heavy endpoints.
Prefixes for correlation. Keep the [HttpLogger] prefix (or set your own) — it makes grep against a shared channel trivial and lets you route on it in log aggregators. If you inject a request ID into the prefix, you get per-request correlation across app and HTTP logs for free.
Test with log assertions, not real files. Because everything funnels through the configured channel, Log::fake() / Log::spy() plus assertions on the channel is the clean way to test that a given route+status produces (or doesn't produce) a log line. This is also how you regression-test sanitization: assert the masked value appears and the raw secret does not.
Custom channels for separation of concerns. Point channel at different channels per environment (e.g. a Slack/critical channel for 5xx in production, a daily file for everything in staging). Since it's just a Laravel log channel name, anything the logging system supports works.
channel names a channel that isn't defined in config/logging.php, you'll get a silent-ish failure or an exception depending on driver. Create the channel before enabling.config/http-logger.php, run php artisan config:clear (or config:cache) — in cached environments nothing you change will take effect otherwise. This is the single most common "it's not logging" cause.StreamedResponse/BinaryFileResponse aren't reliably readable at RequestHandled time. Don't expect download endpoints or SSE streams to produce useful body logs, and consider excluding those routes entirely.routes narrow in production.authorization vs Authorization once rather than assuming.enabled per-request for debugging, expect to need a reload.X-Request-Id) so HTTP logs can be joined to the application log entry that explains the failure.config/logging.php so package upgrades stay clean.How can I help you explore Laravel packages today?