skip to content

Errors, Logs & Debugging

How Laravel reports and renders exceptions, routes log records through channels with context, and exposes state via dumps, Debugbar and Telescope. Interviewers probe it through incidents.

on this pageshow

explore

questions

page 1 of 2

Why must APP_DEBUG be false on a production Laravel app, and what does the debug exception page reveal when it is true?

level: juniorimportance: must knowfreq 62%

answer

  1. config app.debug reads APP_DEBUG
  2. stack frames with source code
  3. request headers and body, unmasked
  4. executed queries with bindings
  5. JSON gets file, line and trace

basics

~20 s

With APP_DEBUG=true an unhandled error renders Laravel's detailed exception page: stack frames with source code, request headers and body, route details and executed SQL. On a public site that hands attackers secrets and internals, so production keeps it false.

solid answer

~40 s

`APP_DEBUG` feeds `config('app.debug')`, which defaults to `false` in `config/app.php` while the skeleton's `.env.example` sets it to `true` for local work. When it is true, an unhandled exception that is not an HTTP exception renders Laravel's exception page: the message and class, stack frames with surrounding source code, the request URL and **all headers** (cookies, `Authorization`), the **request body** as JSON with nothing masked, the route's controller, middleware and parameters, and up to 100 executed queries with bindings filled in. JSON requests get the message, exception class, file, line and a trace. With it false, users get a generic 500 page or `{"message": "Server Error"}`. The docs warn that true in production risks exposing sensitive configuration values; exception messages alone often reveal hosts, users and paths.

code

ini · 7 lines
ini
# .env on a developer laptop
APP_ENV=local
APP_DEBUG=true

# .env on the production server
APP_ENV=production
APP_DEBUG=false

go deeper

for a junior

Know that APP_DEBUG=true shows detailed error pages, that it belongs only on local machines, and that production must set it to false.

for a middle

List what the exception page and the debug JSON body contain, and explain how config('app.debug') is read and cached.

for a senior

Explain the concrete leaks (headers, body, queries, messages) and how to diagnose production errors from logs without enabling debug mode.

for a principal

Build safeguards so debug mode cannot reach production by accident, such as deploy checks and environment-specific configuration review.

## Where the switch lives `config/app.php` contains `'debug' => (bool) env('APP_DEBUG', false)`. The default is `false`; the skeleton's `.env.example` sets `APP_DEBUG=true` because a new project starts as a local development copy. Everything in the framework that asks "am I in debug mode?" reads `config('app.debug')` or `$app->hasDebugModeEnabled()`. ## What debug mode changes When an exception escapes your code, Laravel's exception handler decides what to send back. With debug mode on: - **HTML requests** get Laravel's own exception page for exceptions that are not HTTP exceptions (an `abort(404)` still renders its normal error page). - **JSON requests** get a body with `message`, `exception` (the class), `file`, `line` and `trace` (frames without their arguments). - The framework registers a listener that records executed **queries** so the page can list them. With debug mode off, the same exception produces a plain 500 page, and JSON clients receive `{"message": "Server Error"}`. HTTP exceptions keep their own message in both modes. ## What the exception page shows The page is built by `Illuminate\Foundation\Exceptions\Renderer` and shows: | Section | Content | Why it is dangerous in public | |---|---|---| | Header | exception class and message | messages often embed hosts, usernames, file paths, SQL | | Trace | every frame, with source code around each line | reveals application structure and code | | Request | URL, method and **all headers** | `Cookie` and `Authorization` headers expose sessions and tokens | | Body | `$request->all()` as JSON, unmasked | passwords and card fields from the failing form | | Routing | controller, route name, middleware, parameters | bound models appear with their attributes | | Queries | up to 100 queries with bindings substituted | table layout and real data values | The docs put it bluntly: with `APP_DEBUG` true in production "you risk exposing sensitive configuration values to your application's end users". A database connection failure is a typical example: its message names the host and the user it tried. ## Other things tied to debug mode - **Debugbar**, if installed, only enables itself when debug mode is on and the environment is neither `production` nor `testing`. - Many packages show extra diagnostics only in debug mode, so turning it on "just for a minute" in production enables more than the exception page. ## The grocery app locally On the local copy of the grocery app, debug mode is what you want: when the discount calculation throws, the page shows the failing line in `DiscountService`, the cart id in the route parameters, and the promotion queries that ran just before. That is the fastest route to the bug. The same page on the live store would show every visitor the coupon codes in the request body and the session cookie of whoever triggered it. ## What does not change Debug mode only changes what is **shown**. Reporting is the same either way: the handler still logs the exception with its trace to the configured channels, so production logs keep the detail the page would have displayed. ## Getting detail in production safely 1. Keep `APP_DEBUG=false` and read the **logs**, where the handler reports the full exception and trace. 2. Reproduce locally with debug on, using the log's request details. 3. If you must see more live, add targeted `Log::debug()` lines or context rather than flipping the switch. 4. Apply the same rule to staging and preview servers that testers or clients can reach: anyone who sees the page sees the data. ## Common misunderstandings - Debug mode does not only affect HTML pages; JSON APIs leak file paths and traces too. - Setting `APP_ENV=production` does not turn debug off; the two variables are independent. - Hiding the page behind a custom 500 view does not help if debug mode is still on, because the handler renders the debug page before falling back to views.

  • With APP_DEBUG=false, how do you still find out what caused a production 500 error?
    The exception handler still reports the exception to the log channels with its full message and stack trace, so check `storage/logs` or your log service. Add request identifiers or context to correlate it with the failing request, reproduce locally with debug mode on, and add targeted `Log::debug()` lines if the log is not enough.
  • What does a JSON API client receive for an unhandled exception when APP_DEBUG is true?
    A JSON body with `message`, `exception` (the class name), `file`, `line` and `trace`, an array of stack frames with their arguments removed. With debug off the same failure returns only `{"message": "Server Error"}` for non-HTTP exceptions, which is why an API must also run with debug disabled.

saying these in an interview costs you the question

  • APP_DEBUG only affects HTML pages, so APIs can keep it on.
  • Setting APP_ENV=production automatically disables debug mode.
  • The debug page masks password fields in the request body.
  • A custom errors/500.blade.php view hides the debug page even with APP_DEBUG=true.
  • config/app.php defaults debug to true when APP_DEBUG is missing.
open as a page

In Laravel, what is the difference between the dump() and dd() helpers, and when is Log::debug() the better choice?

level: juniorimportance: must knowfreq 66%

basics

~20 s

dump() prints its arguments and lets the request continue; dd() prints them and stops execution. Log::debug() writes to the log instead of the output, so it suits queued jobs, JSON endpoints and anything whose output you cannot see.

open as a page

In Laravel, how do you give a web app branded error pages for 404 and other HTTP errors, and how do the 4xx and 5xx fallback views work?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Create resources/views/errors/404.blade.php (any status code works); Laravel renders it for an HTTP exception with that status and passes it as $exception. 4xx.blade.php and 5xx.blade.php only cover statuses with no specific page, yours or the framework's.

open as a page

In Laravel, what does the report() helper do, and when would you call it instead of letting an exception propagate?

level: juniorimportance: must knowfreq 50%

basics

~20 s

report($e) passes a Throwable to Laravel's exception handler, which applies its filters, throttling, callbacks and default logging, then returns so your code carries on. Call it in a catch block when a failure must be recorded without failing the request.

open as a page

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%

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.

open as a page

In Laravel, how do you attach data such as an order id to one Log::info() call, and what must never go into that array?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Pass an associative array as the second argument, for example Log::info('Order paid.', ['order_id' => $order->id]); Laravel writes it as JSON after the message. Never put passwords, tokens, card data or a whole $request->all() in it: nothing is redacted.

open as a page

In Laravel 13, how do you customise the response for one exception type with a render callback, a render() method on the exception, or respond()?

level: middleimportance: must knowfreq 50%

basics

~20 s

Register $exceptions->render(function (SeatUnavailableException $e, Request $request) { ... }) in bootstrap/app.php, or give the exception a render($request) method. Return a response to use it, or null/false for the default; respond() adjusts every final exception response.

open as a page

In Laravel 13, how do you register a report callback for one exception type in bootstrap/app.php, and how do you keep that exception out of the default log?

level: middleimportance: must knowfreq 55%

basics

~10 s

Inside withExceptions() in bootstrap/app.php, call $exceptions->report(function (SmsGatewayException $e) { ... }); the closure's type-hint selects the class. Laravel still logs the exception afterwards unless you chain ->stop() or the closure returns false.

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

Using Laravel Telescope, how do you find the slow query behind a sluggish admin page, and how does the query watcher decide a query is slow?

level: middleimportance: must knowfreq 40%

basics

~20 s

Open the admin page's request in Telescope and read its queries: each shows SQL with bindings, duration and the calling file and line. The query watcher tags any query at or above its slow option (100 ms by default) as slow.

open as a page

In a Laravel checkout, a trace id set with Log::withContext() appears in request logs but not in logs of the queued jobs it dispatches. Why, and how do you fix it?

level: seniorimportance: must knowfreq 40%

basics

~20 s

Log::withContext() only changes the web process's logger; the job runs later in a worker with a fresh one. Put the id in the Context facade instead: Laravel serialises Context into each job payload and restores it before the job runs.

open as a page

If Laravel Telescope runs outside local, how do the viewTelescope gate, Telescope::filter() and hideRequestParameters() protect the dashboard and the data it records?

level: seniorimportance: must knowfreq 32%

basics

~20 s

Outside local, the viewTelescope gate decides who may open /telescope; the published version allows nobody until you list users. Telescope::filter() limits recording to exceptions, failed requests and jobs, scheduled tasks and monitored tags, and hideRequestParameters() masks named fields.

open as a page

In Laravel, what do abort(), abort_if() and abort_unless() throw, and how does the status code reach the error response?

level: juniorimportance: should knowfreq 52%

basics

~20 s

abort(404) throws Symfony's NotFoundHttpException and any other code throws HttpException with that status, message and headers; abort_if() and abort_unless() throw only when the condition is true or false. The handler renders the status's error view or JSON.

open as a page

What is Laravel Telescope, how do you install it, and how do you register it so it runs only on local machines?

level: juniorimportance: should knowfreq 40%

basics

~20 s

Telescope is a first-party dashboard that records requests, queries, jobs, exceptions, logs and mail into database tables. Install it, run telescope:install and migrate, and open /telescope; for local-only use, install with --dev and register its providers only in local.

open as a page

In Laravel, what do ->dump(), ->dd() and ->ddRawSql() print when chained on a collection or a query builder?

level: middleimportance: should knowfreq 45%

basics

~20 s

On a collection they dump its items; ->dump() returns the collection so the chain continues, ->dd() stops. On a query builder they dump the SQL, not results: ->dump() shows ? placeholders plus bindings, ->dumpRawSql() and ->ddRawSql() show the SQL with bindings substituted.

open as a page

In a local Laravel app, how does Laravel Debugbar help find why a cart page loads slowly or shows a wrong total, and when does it enable itself?

level: middleimportance: should knowfreq 36%

basics

~20 s

Debugbar injects a toolbar into local HTML pages showing every query with bindings and source line, a timeline, rendered views, log messages and more. It enables itself only when APP_DEBUG is true, the environment is not production or testing, and never in the console.

open as a page

How does Laravel 13 decide whether an exception becomes a JSON or an HTML response, and what does shouldRenderJsonWhen change?

level: middleimportance: should knowfreq 45%

basics

~20 s

By default the handler renders JSON when $request->expectsJson() is true: the first Accept type asks for JSON, or an AJAX request accepts anything. shouldRenderJsonWhen() replaces that test; the Laravel 13 skeleton uses api/* paths or expectsJson().

open as a page

In Laravel 13, which exceptions does the handler never report by default, and how do dontReport, ShouldntReport and stopIgnoring change that?

level: middleimportance: should knowfreq 40%

basics

~10 s

Laravel skips an internal list: HTTP exceptions (so any abort()), validation, authentication, authorization, missing-model, CSRF-token and origin-mismatch errors. dontReport() and the ShouldntReport interface add types, dontReportWhen() adds a condition, stopIgnoring() removes a listed class.

open as a page

In Laravel 13, how do you control the log level and the extra context an exception's default log entry carries?

level: middleimportance: should knowfreq 28%

basics

~10 s

$exceptions->level(Type::class, LogLevel::WARNING) sets the PSR-3 level for a type; unmapped exceptions log at error. Context comes from the exception's context() method and $exceptions->context() closures, plus the logged-in user's id and the exception itself.

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 make a Laravel 13 app write JSON log records, and where do per-call context and Context facade data land in each record?

level: middleimportance: should knowfreq 30%

basics

~20 s

Set a channel's 'formatter' option in config/logging.php to a JSON formatter class, such as Monolog's JsonFormatter or Laravel's own since 13.6. Per-call and withContext data land under the record's context key; Context facade data lands under extra.

open as a page

In Laravel, how do Log::withContext() and Log::shareContext() differ, and which log channels receive the data each one adds?

level: middleimportance: should knowfreq 42%

basics

~10 s

Log::withContext() merges data into the default channel only, so entries sent through Log::channel('slack') lack it. Log::shareContext() adds it to every channel already resolved and every channel created afterwards, including on-demand stacks.

open as a page

In Laravel Telescope's config/telescope.php, how are watchers switched on and tuned, and which defaults of the log, request and model watchers surprise people?

level: middleimportance: should knowfreq 30%

basics

~20 s

Each watcher is a class key in the watchers array, set to a boolean or to an options array with enabled. Surprising defaults: the log watcher records only error and above, and the request watcher keeps at most 64 KB of response.

open as a page

What goes wrong when a stray dd() or dump() reaches a deployed Laravel app, in a JSON endpoint, a queued job or a Blade view?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Dumps are not gated by APP_DEBUG: they still print. In a JSON endpoint they corrupt the body, in a Blade view every visitor sees the data, and dd() in a queued job exits the whole worker, leaving the job to reappear after retry_after.

open as a page

In a Laravel 13 API, a missing booking returns a 404 naming App\Models\Booking, and a render callback typed ModelNotFoundException never runs; why, and what is the fix?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Before render callbacks run, Laravel's handler converts ModelNotFoundException into a NotFoundHttpException that keeps its 'No query results for model [...]' message, so the callback never matches. Type-hint NotFoundHttpException, check getPrevious(), and return your own body.

open as a page

An SMS provider outage makes a Laravel 13 app report thousands of identical exceptions a minute; how do you throttle that reporting with withExceptions()?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Register $exceptions->throttle() in bootstrap/app.php and return Limit::perMinute(n), optionally ->by() a key, for the SMS exception, or Lottery::odds(1, 1000) to sample. Returning null leaves other exceptions unthrottled.

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

In Laravel, how do Context::addHidden() and the Context::dehydrating() and hydrated() callbacks let a queued job restore request state such as the locale without logging it?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

Context::addHidden() stores values that travel into queued jobs but are never appended to log records. A Context::dehydrating() callback adds request state such as app.locale to the copy sent with the job; a Context::hydrated() callback restores it in the worker.

open as a page

showing 1–30 of 31