skip to content

Channels & Stacks

Laravel's config/logging.php sends Monolog records to channels such as single, daily, stack, slack and stderr, each with its own minimum level. Interviewers ask how logs reach container output.

on this pageshow

explore

questions

6

In a Laravel 13 app, what do LOG_CHANNEL, LOG_STACK and LOG_LEVEL control, and where do log entries go by default?

level: juniorimportance: must knowfreq 60%

answer

  1. config/logging.php 'default' key
  2. a stack wrapping single
  3. storage/logs/laravel.log
  4. LOG_LEVEL=debug in .env.example
  5. LOG_STACK=daily for rotating files

basics

~10 s

LOG_CHANNEL picks the default channel (stack), LOG_STACK lists the channels that stack writes to (single), and LOG_LEVEL is the minimum level for channels that read it (debug). So Log::info() lands in storage/logs/laravel.log.

solid answer

~40 s

All logging is configured in `config/logging.php`. `'default' => env('LOG_CHANNEL', 'stack')` names the channel that `Log::info()` and the exception handler use. The skeleton's `stack` channel reads `LOG_STACK` (default `single`) as a comma-separated list, so by default everything goes through `stack` to `single`, which writes `storage/logs/laravel.log`. `LOG_LEVEL`, `debug` in `.env.example`, is not a global switch: it is the `level` value most skeleton channels read, and each channel drops entries below its own level. To rotate files, set `LOG_STACK=daily` (one file per day, `LOG_DAILY_DAYS` kept, 14 by default) or, since Laravel 13.23, `monthly`. Several channels are a list such as `LOG_STACK=daily,slack`. Level names must be the lowercase PSR-3 names; an invalid value makes the channel fail and Laravel falls back to its emergency logger.

code

ini · 4 lines
ini
LOG_CHANNEL=stack
LOG_STACK=daily
LOG_LEVEL=info
LOG_DAILY_DAYS=30

go deeper

for a junior

Know that logs land in storage/logs/laravel.log by default and that LOG_CHANNEL and LOG_STACK choose where they go.

for a middle

Explain how the stack reads LOG_STACK, how each channel's level filters entries, and how to switch to daily files.

for a senior

Pick levels and rotation for production, and catch silent emergency-logger fallbacks caused by bad level names or channel typos.

for a principal

Standardise logging configuration across services so the same variables mean the same thing everywhere they are deployed.

## Where logging is configured Laravel logs through **channels**. A channel is a named entry in `config/logging.php` with a **driver** (how and where to write) and options such as a file path and a minimum level. Under the hood each channel becomes a Monolog logger. Three environment variables in the skeleton's `.env.example` steer the defaults: ```ini LOG_CHANNEL=stack LOG_STACK=single LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug ``` ## What each variable does - **`LOG_CHANNEL`** feeds `'default' => env('LOG_CHANNEL', 'stack')`. The default channel receives everything written through the `Log` facade without an explicit channel, and every exception the handler reports. - **`LOG_STACK`** feeds the skeleton's `stack` channel: `'channels' => explode(',', (string) env('LOG_STACK', 'single'))`. A stack writes each entry to every channel in its list, so `LOG_STACK=daily,slack` means both. - **`LOG_LEVEL`** is read as `'level' => env('LOG_LEVEL', ...)` by most channels in the skeleton: `single`, `daily`, `monthly`, `syslog`, `errorlog`, `stderr`, a remote-syslog example channel and, with a `critical` fallback, `slack`. A channel ignores entries below its level. It is a shared default, not a global filter: a channel that hard-codes its own `level` ignores it. ## The default path, step by step 1. Code calls `Log::info('Driver matched to ride')`. 2. The log manager resolves the default channel, `stack`. 3. The stack lists `single`, from `LOG_STACK`. 4. `single` uses a stream handler on `storage/logs/laravel.log` with level `debug`, so the `info` entry is appended. The same file therefore holds every level from `debug` upward, and it grows until something rotates or truncates it. ## The built-in drivers | Driver | Writes to | Notes | |---|---|---| | `single` | One file | `storage/logs/laravel.log` in the skeleton | | `daily` | One file per day | `max_files` from `LOG_DAILY_DAYS`, 14 in the skeleton | | `monthly` | One file per month | Added in Laravel 13.23; keeps 3 files | | `stack` | Other channels | Fans out; has no level of its own | | `slack` | A Slack incoming webhook | Needs `LOG_SLACK_WEBHOOK_URL` | | `syslog` | The system log | Ident taken from `app.name` | | `errorlog` | PHP's `error_log()` | Wherever the PHP runtime sends it | | `monolog` | Any Monolog handler | The skeleton's `stderr` and `null` channels use it | | `custom` | A factory you write | Full control over the logger | Note that `null` in the skeleton is a *channel* built with the `monolog` driver and Monolog's `NullHandler`, not a driver of its own; it is where deprecations go by default. ## Reading a default log line Laravel formats file and stream entries with Monolog's line formatter, so a line in `laravel.log` looks like: ``` [2026-09-29 10:15:02] local.INFO: Driver accepted ride ``` - The timestamp uses Laravel's `Y-m-d H:i:s` date format. - `local` is the Monolog **channel name**. Unless a channel sets a `name` option, Laravel uses the current environment, so production entries read `production.ERROR`; it is not the name of the `config/logging.php` channel. - `INFO` is the level, followed by the message and, when present, the context array; empty context is omitted. - Exceptions logged by the handler include their stack trace on the following lines. Knowing this helps when searching: `grep 'production.ERROR' storage/logs/laravel.log` finds errors whatever channel wrote them. ## Typical changes - **Rotation for a long-running server:** `LOG_STACK=daily` and, if needed, `LOG_DAILY_DAYS=30`. - **Quieter production logs:** `LOG_LEVEL=info` or `warning`, so `Log::debug()` calls stop being written. - **Adding an alert channel:** `LOG_STACK=daily,slack`, with the Slack channel's level kept at `critical`. - **Containers:** switch to the skeleton's `stderr` channel instead of files. ## Pitfalls - Level names are the lowercase PSR-3 names. `LOG_LEVEL=WARNING` or `LOG_LEVEL=warn` is rejected as an invalid level; the channel cannot be built, and Laravel writes through an **emergency logger** to `storage/logs/laravel.log` instead, adding an `Unable to create configured logger` entry. - A misspelled channel name in `LOG_CHANNEL` or `LOG_STACK` fails the same way. - Setting `LOG_LEVEL=error` silences `Log::info()` everywhere that reads it, which surprises people who wanted only Slack to be quiet. - Leaving `single` in production: one file that grows without limit until the disk fills. Use `daily` or `monthly` with `max_files`, or ship logs off the machine.

  • What happens if LOG_STACK names a channel that is not defined?
    The log manager cannot build that channel, so it falls back to an emergency logger that writes to `storage/logs/laravel.log` (or the `emergency` channel's `path`) and records an 'Unable to create configured logger' entry. Nothing throws, so the typo can go unnoticed.
  • Does LOG_LEVEL filter entries for every channel?
    No. It is only the value that channels read for their own `level` option. A channel with a hard-coded `level`, or one that reads a different variable, is unaffected, and the `slack` channel falls back to `critical` only when `LOG_LEVEL` is unset.

saying these in an interview costs you the question

  • LOG_LEVEL is a global filter applied before any channel
  • Laravel writes one file per day by default
  • LOG_LEVEL=WARNING works because level names are case-insensitive
  • A stack channel has its own minimum level
  • A typo in LOG_CHANNEL throws an exception on the next log call
open as a page

In Laravel, how do you send critical errors to Slack and everything else to daily files with a stack channel, and what decides which channel writes each entry?

level: middleimportance: must knowfreq 55%

basics

~20 s

List daily and slack in the stack channel, give slack a level of critical and daily a lower one. The stack offers every entry to each channel, and a channel writes it only at or above its own minimum level.

open as a page

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%

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.

open as a page

In Laravel's config/logging.php, how do tap classes and the monolog and custom drivers let you customise a log channel beyond the built-in drivers?

level: middleimportance: should knowfreq 25%

basics

~20 s

A tap class on a channel receives the built Illuminate\Log\Logger once, to add processors or adjust handlers. The monolog driver builds any Monolog handler from handler and handler_with; a custom driver's via factory returns a whole Monolog logger.

open as a page

How do you get a Laravel 13 app's logs into a container's stderr, and why can entries still vanish when the app runs under PHP-FPM?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Point logging at the skeleton's stderr channel with LOG_CHANNEL=stderr or LOG_STACK=stderr; it is a Monolog StreamHandler on php://stderr. Under PHP-FPM, worker stderr is discarded unless the pool sets catch_workers_output = yes.

open as a page

In Laravel 13, how do you log PHP and framework deprecation warnings, and why do none appear in a freshly installed app?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

Laravel sends E_DEPRECATED and E_USER_DEPRECATED notices to its deprecations channel, which the skeleton points at the null channel. Set LOG_DEPRECATIONS_CHANNEL to a real channel, or define a channel named deprecations, and they are logged as warnings.

open as a page