skip to content

In Laravel, when would you write with Log::channel(), Log::stack() or Log::build() instead of the default log channel?

level: middleimportance: should knowfreq 40%

answer

  1. channel('name') from logging.php
  2. stack([...]) fans out ad hoc
  3. build([...]) needs no config entry
  4. a built channel can join a stack
  5. unknown names hit the emergency logger

basics

~10 s

Log::channel('payouts') writes to one configured channel, Log::stack(['daily', 'slack']) sends one entry to several channels, and Log::build([...]) creates a channel from an inline config array. Plain Log::info() uses the default channel.

solid answer

~40 s

The `Log` facade writes to the default channel, which suits most application logging. `Log::channel('payouts')` targets one channel defined in `config/logging.php`, for example a separate daily file for driver payouts that finance reviews; instances are resolved once and cached by name. `Log::stack(['daily', 'slack'])` builds an ad hoc stack for one call site, offering the entry to each listed channel, each still applying its own level. `Log::build(['driver' => 'single', 'path' => storage_path('logs/import.log')])` creates an on-demand channel from an array with no config entry, useful for one-off commands; it is rebuilt on every call, and a built channel can be placed inside a `Log::stack([...])` array. Naming a channel that does not exist does not throw: Laravel falls back to its emergency logger and writes an 'Unable to create configured logger' entry.

code

php · 25 lines
php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

class SettleDriverPayouts extends Command
{
    protected $signature = 'payouts:settle';

    public function handle(): void
    {
        Log::channel('payouts')->info('Settlement started');

        $run = Log::build([
            'driver' => 'single',
            'path' => storage_path('logs/settlement-'.now()->format('Y-m-d').'.log'),
        ]);

        $run->info('Batch 1 settled');

        Log::stack(['payouts', 'slack'])->critical('Settlement aborted: bank API rejected batch 2');
    }
}

go deeper

for a junior

Know that Log::channel('name') writes to one configured channel and plain Log::info() uses the default.

for a middle

Explain when stack() and build() fit, that levels still apply, and what happens with an unknown channel name.

for a senior

Keep routing in configuration, reserve explicit channels for separate audiences, and watch for misnamed channels landing in the fallback file.

for a principal

Decide which log streams are separate products with their own retention and owners, and name them once.

## The default and when to leave it `Log::info()`, `Log::error()` and the other level methods on the `Log` facade write to the **default channel**, `logging.default`, usually the skeleton's `stack`. For almost all application logging that is right: one configuration decides where entries go, and it can change per environment through `.env` without touching code. Reach for an explicit channel only when a particular stream of entries has a different audience or lifetime. ## Log::channel(): a named channel `Log::channel('payouts')` returns the logger for a channel defined under `channels` in `config/logging.php`. - Use it for **separate audiences**: a `payouts` daily file for finance, an `audit` file kept longer, a `dispatch` channel for the operations team. - The name may be a string or an enum. - The log manager resolves each channel once and caches it by name, so repeated calls are cheap. - `Log::channel()` with no argument returns the default channel. Writing to a named channel hard-codes the destination at the call site. Use it when the destination is part of the requirement ("payout records go to the payout log"), not as a way to raise severity. ## Log::stack(): an ad hoc fan-out `Log::stack(['daily', 'slack'])` builds a stack from the listed channels for that call and returns a logger: - every listed channel is offered the entry, and each applies its own `level`; - the optional second argument sets the Monolog channel name shown in the entries; - the list may contain channel names or logger instances. It suits a call site that must reach more than the default, such as a nightly settlement command that writes to both its own file and the alert channel. ## Log::build(): a channel with no config entry `Log::build([...])` takes a full channel configuration array and returns a logger built from it: ```php Log::build([ 'driver' => 'single', 'path' => storage_path('logs/driver-import.log'), ])->info('Imported 1,240 driver profiles'); ``` - Nothing needs to exist in `config/logging.php`, which keeps one-off commands and scripts from cluttering it. - The on-demand channel is rebuilt on every `build()` call; keep the returned logger in a variable if you write many entries. - A built logger can be combined with named channels: `Log::stack(['slack', $channel])`. ## Choosing between them | Need | Use | |---|---| | Normal application logging | `Log::info()` on the default channel | | A permanent separate stream with its own audience | A channel in `config/logging.php` plus `Log::channel()` | | One call site that must reach several channels | `Log::stack([...])` | | A temporary or script-specific file | `Log::build([...])` | ## What happens with a wrong name If `Log::channel('payout')` names a channel that is not configured, the log manager cannot build it. It does **not** throw. It returns an **emergency logger** writing to `storage/logs/laravel.log` (or the `emergency` channel's `path`) and first logs an `Unable to create configured logger. Using emergency logger.` entry. The same happens for invalid config inside `Log::build()`. The entry is not lost, but it lands in the wrong place, so typos in channel names show up only when someone reads the fallback file. ## Channel instances live for the process The log manager caches every resolved channel for the life of the PHP process. Under PHP-FPM that means one request, so it rarely matters. In a queue worker, the scheduler's long-running command or an application server that keeps the app in memory, it means: - a channel's handlers, taps and levels are fixed when it is first used; changing configuration requires restarting the process; - a daily channel keeps rotating correctly, because the rotating handler checks the date on each write; - `Log::forgetChannel('payouts')` discards a cached instance when you really need it rebuilt; - `Log::stack()` and `Log::build()` create new loggers on each call, so they are not cached the same way. ## Common mistakes - Using `Log::channel('slack')` to "make it important"; severity belongs in the level, and routing in configuration. - Calling `Log::build()` inside a loop and rebuilding the channel every iteration. - Spreading channel names as string literals across the code base; a constant or enum keeps them consistent.

  • Does Log::stack(['daily', 'slack']) bypass each channel's level?
    No. The stack collects the channels' handlers, and each handler still writes only entries at or above its own `level`. A warning sent through that stack reaches `daily` but not a critical-only `slack` channel.
  • Why keep the logger returned by Log::build() in a variable?
    Each `Log::build()` call discards the previous on-demand channel and builds a new one. Storing the returned logger and reusing it avoids rebuilding the handler for every entry.

saying these in an interview costs you the question

  • Log::channel() with an unknown name throws an exception
  • Log::build() needs a matching entry in config/logging.php
  • Log::stack() makes every channel write regardless of level
  • Log::channel('slack') is the right way to mark an entry urgent
  • Log::info() always writes to the single channel