In Laravel, when would you write with Log::channel(), Log::stack() or Log::build() instead of the default log channel?
answer
- channel('name') from logging.php
- stack([...]) fans out ad hoc
- build([...]) needs no config entry
- a built channel can join a stack
- unknown names hit the emergency logger
basics
~10 sLog::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 sThe `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
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
Know that Log::channel('name') writes to one configured channel and plain Log::info() uses the default.
Explain when stack() and build() fit, that levels still apply, and what happens with an unknown channel name.
Keep routing in configuration, reserve explicit channels for separate audiences, and watch for misnamed channels landing in the fallback file.
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