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 Http Logger Laravel Package

spatie/laravel-http-logger

This Laravel middleware logs incoming HTTP requests to your default log, so you can still access the original request data if something goes wrong. An extra safety net for critical user submissions like lead-generating forms, with configurable log profile, writer, and channel.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish the config if you want to tune behavior:

composer require spatie/laravel-http-logger

php artisan vendor:publish \
  --provider="Spatie\HttpLogger\HttpLoggerServiceProvider" \
  --tag="config"

The first practical use case is a critical form submission—leads, orders, applications—where you want the original request body even if validation, a downstream API, or the database fails. Start by attaching the middleware to one route rather than globally:

use Spatie\HttpLogger\Middlewares\HttpLogger;

Route::post('/leads', StoreLeadController::class)
    ->middleware(HttpLogger::class);

For Laravel 11+ global registration:

->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\Spatie\HttpLogger\Middlewares\HttpLogger::class);
})

For Laravel 10 and below, add it to app/Http/Kernel.php under $middleware.

Look first at:

  • config/http-logger.php — enabled, log_profile, log_writer, log_channel, log_level, except, sanitize_headers
  • Spatie\HttpLogger\Middlewares\HttpLogger — the middleware entry point
  • Spatie\HttpLogger\LogNonGetRequests — default profile, logs POST, PUT, PATCH, DELETE, and QUERY
  • Spatie\HttpLogger\DefaultLogWriter — default message format and header/body handling

For a quick sanity check, set HTTP_LOGGER_ENABLED=true, submit a test POST, and tail your configured log channel. Also remember: if config is cached, environment changes require php artisan config:clear before retesting.

Implementation Patterns

Start route-scoped, then widen deliberately. Global middleware is convenient but will evaluate every request and can produce a lot of noise. Route or route-group middleware is usually the right default:

Route::middleware(\Spatie\HttpLogger\Middlewares\HttpLogger::class)
    ->group(function () {
        Route::post('/leads', StoreLeadController::class);
        Route::post('/orders', StoreOrderController::class);
    });

Use a custom LogProfile when the default non-GET rule is too broad. The profile only decides whether a request should be logged:

namespace App\Support\HttpLogger;

use Illuminate\Http\Request;
use Spatie\HttpLogger\LogProfile;

class LogCriticalSubmissions implements LogProfile
{
    public function shouldLogRequest(Request $request): bool
    {
        return $request->isMethod('post')
            && ($request->routeIs('leads.store') || $request->routeIs('orders.store'))
            && ! $request->is('health*');
    }
}
// config/http-logger.php
'log_profile' => App\Support\HttpLogger\LogCriticalSubmissions::class,

Use a custom LogWriter when you want structured, queryable logs. The default writer produces a human-readable line. For production triage, a JSON context line with route, user, IP, and request ID is much easier to search:

namespace App\Support\HttpLogger;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Spatie\HttpLogger\LogWriter;

class StructuredHttpLogWriter implements LogWriter
{
    public function logRequest(Request $request): void
    {
        $sanitizedHeaders = array_map(
            'strtolower',
            config('http-logger.sanitize_headers', [])
        );

        $headers = collect($request->headers->all())
            ->map(fn (array $values) => $values[0] ?? null)
            ->map(fn ($value, string $name) => in_array($name, $sanitizedHeaders, true)
                ? '****'
                : $value)
            ->all();

        Log::channel(config('http-logger.log_channel'))
            ->log(config('http-logger.log_level', 'info'), 'http.request', [
                'method' => strtoupper($request->method()),
                'uri' => $request->getPathInfo(),
                'route' => $request->route()?->getName(),
                'ip' => $request->ip(),
                'user_id' => $request->user()?->id,
                'request_id' => $request->header('X-Request-Id'),
                'body' => $request->except(config('http-logger.except', [])),
                'headers' => $headers,
            ]);
    }
}
'log_writer' => App\Support\HttpLogger\StructuredHttpLogWriter::class,

Give HTTP logs their own channel and retention policy. Do not let critical request logs compete with or duplicate into your application’s default stack:

// config/logging.php
'http-logger' => [
    'driver' => 'daily',
    'path' => storage_path('logs/http-logger.log'),
    'level' => 'info',
    'days' => 14,
],
// config/http-logger.php
'log_channel' => env('HTTP_LOGGER_CHANNEL', 'http-logger'),
HTTP_LOGGER_CHANNEL=http-logger

Sanitize both body and headers. except only filters request body fields; it does not redact headers. Add sensitive headers explicitly:

'excerpt' => [
    'password',
    'password_confirmation',
    'token',
    'card_number',
],

'sanitize_headers' => [
    'Authorization',
    'Cookie',
    'Set-Cookie',
    'X-Api-Key',
],

Use a custom writer if you need an allowlist rather than a denylist for body fields—especially for forms with nested payloads.

Disable logging in tests and local noise-heavy environments. Set HTTP_LOGGER_ENABLED=false in phpunit.xml or the test environment, and keep it enabled only where the safety net is valuable. For assertions, point http-logger.log_writer at a small fake writer in the test and assert what it received.

Gotchas and Tips

  • HTTP_LOGGER_ENABLED is read through config. If you change it in .env and use config:cache, run php artisan config:clear and recache. A stale config is the most common “why is it still logging?” issue.
  • The default profile does not log GET requests. That is usually correct because GET query strings often contain tokens or PII, but if you need GET logging, write a custom profile and scrub query parameters carefully.
  • QUERY method support requires a recent version. It was added in 1.12.0, which also dropped PHP < 8.3 and Laravel < 12. Pin the package version if you rely on QUERY requests.
  • except is body-only and top-level by default in the published config. Nested payloads such as user.password or payment.card can still leak. Verify with a real payload and use a custom writer with explicit allowlisting for high-risk forms.
  • sanitize_headers must cover Authorization, Cookie, Set-Cookie, and API-key headers. The default writer includes headers in recent versions, so an unsanitized token will end up in logs.
  • Do not log whole multipart uploads, base64 payloads, or file contents. Log file names, MIME types, and sizes instead. Large request bodies also create huge log lines and slow down log ingestion.
  • There is no built-in sampling, deduplication, or retention policy. Route-scoped middleware plus a custom profile is your sampling mechanism. Use a dedicated daily channel with a short retention period.
  • Writing to the default stack channel can duplicate logs. If the stack contains multiple handlers, the same request may be written multiple times. Use a single dedicated channel for HTTP request logs.
  • An exception in a custom writer can fail the request. Logging is a safety net, not core business logic. If you do external I/O in a writer, wrap it in try/catch so logging failures do not break submissions.
  • The package logs requests, not responses. You will not get status codes, exception classes, or response bodies by default. Add a request ID or trace ID in your writer and correlate with your application/error logs.
  • Middleware order affects the logged body. If the logger runs after Laravel’s global input-trimming middleware, it sees the transformed input. That is usually fine, but if you need the raw request, verify registration order.
  • Treat these logs as sensitive production data. They contain user-submitted PII and potentially credentials. Restrict file access, define retention, and avoid shipping them to third-party log systems without redaction.
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.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk