Reader Stacks

Writing Custom Log Files (Log Channels) in Laravel

Laravel logs everything to a single laravel.log by default. Here is how to split payment, webhook, or auth events into their own dedicated log files with a custom channel.

By default, everything goes into one storage/logs/laravel.log file, which gets noisy fast. A custom log channel routes specific events — payments, webhooks, a third-party integration — into their own file, making them far easier to grep or monitor separately.

1. Define the channel

In config/logging.php, add a new entry to the channels array:

'channels' => [
    // ...existing channels

    'payments' => [
        'driver' => 'daily',
        'path' => storage_path('logs/payments.log'),
        'level' => env('LOG_LEVEL', 'debug'),
        'days' => 30,
    ],
],

The daily driver rotates to a new dated file each day and prunes files older than days automatically — a plain single driver works too, but grows forever without rotation.

2. Log to it explicitly

use Illuminate\Support\Facades\Log;

Log::channel('payments')->info('Charge succeeded', [
    'order_id' => $order->id,
    'amount' => $order->total,
]);

Always pass structured context as the second array argument rather than interpolating values into the message string — it keeps the log machine-parseable if you ever pipe it into a log aggregator.

3. Optional: send the same entry to multiple channels

A stack channel fans a single log call out to several channels at once — useful for "log to file AND alert Slack on payment failures":

'payments_stack' => [
    'driver' => 'stack',
    'channels' => ['payments', 'slack'],
],

Common mistake

Forgetting to clear config cache after editing config/logging.php in a production environment where php artisan config:cache has been run — the app keeps using the cached (old) config until you run php artisan config:cache again, not just config:clear.

Choose single versus daily based on retention, not habit

A single file is easy to inspect but needs an external rotation policy once it can grow indefinitely. The daily driver handles dated rotation and retention itself. In containers or horizontally-scaled deployments, local files may be ephemeral or split across hosts; in that architecture, structured logs to a centralized collector can be more useful than inventing more per-server files.

Do not log secrets just because the channel is private

Payment and webhook logs are exactly where developers are tempted to dump whole request payloads. Redact authorization headers, API keys, session tokens, passwords, and payment data before it reaches any log channel. File permissions and a separate filename reduce accidental exposure; they do not make sensitive payloads safe to retain.

Give context fields stable names

Structured context pays off when the same field means the same thing across entries. Prefer order_id, provider, event_id, and duration_ms consistently over embedding those values in prose. A log aggregator can then filter and aggregate them without parsing human sentences.

Use stacks for delivery policy, not duplicate application calls

If every critical payment error should go to a file and an alerting destination, configure that fan-out in a stack rather than calling Log::channel(...) twice throughout the codebase. The application emits one event at one severity; logging configuration decides where that event travels. This keeps operational routing out of payment business logic and makes it changeable without editing every call site.

Make filesystem failures observable

A dedicated log file is useful only if the PHP process can write to its directory. After deployment, verify permissions and disk space; a channel configuration can be syntactically correct while the underlying handler fails to create or rotate the file.

Topics: Developer Productivity Debugging & Testing