Reader Stacks

Creating Custom Log Channels in Laravel

A dedicated log channel keeps one specific event stream (payments, webhooks) in its own file — genuinely easier to monitor and grep than digging it out of the shared laravel.log alongside everything else.

Laravel's default logging writes everything to one shared laravel.log file — a dedicated custom channel routes a specific category of event (payment processing, webhook payloads, a background job's activity) into its own separate file, genuinely easier to monitor and grep in isolation.

Defining a custom channel

// config/logging.php
'channels' => [
    // ... existing channels

    'payments' => [
        'driver' => 'single',
        'path' => storage_path('logs/payments.log'),
        'level' => 'debug',
    ],
],

Writing to the custom channel

use Illuminate\Support\Facades\Log;

Log::channel('payments')->info('Payment processed', [
    'order_id' => $order->id,
    'amount' => $order->total,
    'gateway' => 'stripe',
]);

Log::channel('payments') is what routes this specific call to payments.log instead of the default channel — every other Log::info()/Log::error() call elsewhere in the app, without specifying a channel, continues writing to the default laravel.log untouched.

Using a daily rotating channel instead of a single file

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

The daily driver creates a new dated log file each day (payments-2026-05-01.log) and automatically deletes files older than the configured days — genuinely useful for a high-volume log that would otherwise grow into an unmanageably large single file with the single driver.

Setting a custom channel as one of several via the stack driver

'stack' => [
    'driver' => 'stack',
    'channels' => ['single', 'payments'],
],

The stack driver fans a single log call out to multiple underlying channels at once — this specific configuration means anything logged through the default stack also gets written to both files, useful when a log entry belongs in the general application log and a dedicated one simultaneously.

Logging structured context data

Log::channel('payments')->info('Payment failed', [
    'order_id' => $order->id,
    'error_code' => $exception->getCode(),
    'gateway_response' => $response->json(),
]);

Passing an array as the second argument attaches structured context to the log entry — genuinely more useful than concatenating everything into one long message string, since many log viewers and log-aggregation tools can parse and filter on these structured context fields directly.

Creating a channel with a custom formatter (advanced)

'payments' => [
    'driver' => 'monolog',
    'handler' => Monolog\Handler\StreamHandler::class,
    'with' => [
        'stream' => storage_path('logs/payments.log'),
    ],
    'formatter' => Monolog\Formatter\JsonFormatter::class,
],

Laravel's logging system is built directly on Monolog, and the monolog driver exposes this underlying flexibility — a JSON formatter (as shown) is genuinely useful when log entries are meant to be ingested by an external log-aggregation service that expects structured JSON lines rather than Laravel's default human-readable text format.

Why a dedicated channel matters beyond just organization

Beyond simply keeping logs organized, a dedicated channel with its own retention period, log level, and format lets each category of logged event be tuned independently — a payments channel might genuinely need a much longer retention period than a routine debug channel, and mixing them into one shared file makes that kind of independent tuning impossible.