skip to content

Handlers & Reporting Levels

PHP still raises warnings and notices outside the exception tree, filtered by E_* levels and routed through set_error_handler. Interviewers ask how you surface them without leaking them to users.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

6

In PHP, what is the difference between the display_errors and log_errors settings, and how should each be set in production?

level: middleimportance: must knowfreq 62%

answer

  1. one writes to output, one to a log
  2. built-in: display On, log Off
  3. php.ini-production flips both
  4. error_log unset: the SAPI's logger
  5. error_reporting filters both

basics

~20 s

display_errors writes error messages into the script's output, where visitors see them; log_errors writes them to the error log. Production should run display_errors=Off and log_errors=On, the values php.ini-production ships, so errors are recorded but never shown.

solid answer

~50 s

`display_errors` decides whether PHP prints warnings, notices and fatal errors into the response or terminal; `log_errors` decides whether it writes them to a log. The two are independent, and both only see what `error_reporting` lets through. The built-in defaults are the wrong way round for a server — with no php.ini, `display_errors` is On and `log_errors` is Off — so production relies on php.ini-production, which ships `display_errors = Off`, `display_startup_errors = Off`, `log_errors = On` and `error_reporting = E_ALL & ~E_DEPRECATED`. The destination is `error_log`: a file path writable by the PHP process, the special value `syslog`, or unset, in which case messages go to the SAPI's own logger — typically the web server's error log for a web request, stderr for the CLI. Displayed errors leak paths, SQL and sometimes credentials; logged errors are what you monitor.

code

ini · 11 lines
ini
; production
error_reporting = E_ALL & ~E_DEPRECATED
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/booking-errors.log

; development
; error_reporting = E_ALL
; display_errors = On
; display_startup_errors = On

go deeper

for a junior

Recall that display_errors prints errors into the output and log_errors writes them to a log, and that production turns display off and logging on.

for a middle

Explain the built-in defaults versus php.ini-production, how error_reporting filters both outputs, and the three things error_log can be set to.

for a senior

Show how a missing php.ini or an unwritable log file sends errors somewhere unexpected, and how to check which settings the serving process actually uses.

for a principal

Define a standard error configuration across environments and images so that no deployment can start with display on and logging off.

## Two independent switches PHP has one filter and two outputs for its non-exception diagnostics (warnings, notices, deprecations, fatal errors): - **`error_reporting`** is the filter: a bitmask of the `E_*` levels PHP reports at all. - **`display_errors`** sends a reported error into the **output** — the HTML page, the JSON body, the terminal. - **`log_errors`** writes a reported error to the **error log**. Each output is switched on or off separately, so all four combinations are possible. Development usually wants both on; production wants logging only. | Directive | Built-in default (no php.ini) | php.ini-development | php.ini-production | |---|---|---|---| | `display_errors` | On | On | Off | | `display_startup_errors` | On | On | Off | | `log_errors` | Off | On | On | | `error_reporting` | `E_ALL` | `E_ALL` | `E_ALL & ~E_DEPRECATED` | The first column is the trap. A server started without a php.ini — a bare container image is the usual way this happens — shows errors to visitors and logs nothing. Always ship an explicit production configuration. `display_errors` also accepts `stderr`, which sends displayed errors to standard error instead of standard output; the shipped ini notes that this affects only the CGI and CLI binaries. ## Where logged errors go `log_errors` only says *whether* to log. The **`error_log`** directive says *where*: 1. **A file path**, such as `/var/log/php/booking-errors.log`. The file must be writable by the user PHP runs as. If PHP cannot open it, it does not give up: it falls back to the SAPI's logger, so the message lands somewhere other than where you are looking. 2. **`syslog`**, which sends messages to the system logger (the Event Log on Windows). 3. **Unset** (the default), which hands messages to the **SAPI's own logger**: for a web request that is typically the web server's error log — under PHP-FPM the line is prefixed `PHP message:` — and for the CLI it is stderr. ## Why display must be off in production Error text is written for developers, and it contains what an attacker wants: - absolute file paths and the layout of the application; - fragments of SQL, and the names of tables and columns; - occasionally a DSN, an API key or a password passed as an argument; - the PHP version and the libraries in use. Displayed errors also **corrupt responses**: a warning printed before a `header()` call makes the header fail with "headers already sent", and a warning inside a JSON response makes it unparseable for the client. `display_startup_errors` is separate from `display_errors` because startup failures (a missing extension, a bad ini value) happen before a script runs; php.ini-production turns it off too. ## Reading the log well A few related directives shape what ends up in the log: - `ignore_repeated_errors` (Off in the shipped ini files) collapses the same message from the same file and line; - `html_errors` formats messages as HTML and is hardcoded Off for the CLI; it matters only for displayed errors; - `error_log_mode` sets the permissions of a newly created log file (default `0644`). ## A sensible production baseline - `display_errors = Off` and `display_startup_errors = Off`; - `log_errors = On`, with `error_log` pointing at a file your log shipper collects, or unset if the web server's log is already collected; - `error_reporting = E_ALL & ~E_DEPRECATED`, or plain `E_ALL` if you want deprecations in production logs ahead of an upgrade; - application-level handlers that **also** log, rather than replacing PHP's logging with a blank page. ## Misconfigurations that turn up in audits - `error_log` pointing **inside the web root**, for example `public/php-errors.log`: the log becomes downloadable by anyone who guesses the name, which is worse than displaying errors. - A log file on a volume that fills up: writes fail, and the messages fall back to the SAPI's logger, so the configured file simply stops growing. - `display_errors = On` left in a staging configuration that was copied to production. - `error_reporting = 0` used to "clean up" noisy logs, which also hides every real failure. - Relying on the built-in defaults in a container image that ships no php.ini at all. Each of these leaves a server that either leaks errors to visitors or records nothing — the two outcomes this pair of switches exists to prevent.

  • What happens when error_log names a file the PHP process cannot write?
    PHP tries to open the file for each message; when that fails, it falls back to the SAPI's logger — typically the web server's error log for a web request, stderr for the CLI. Nothing is lost, but errors appear somewhere other than the file you are watching, which is a common reason a production log looks empty.
  • Is it enough to set error_reporting = 0 in production instead of turning display_errors off?
    No. `error_reporting = 0` stops PHP from reporting errors at all, so they are neither displayed nor logged, and you lose the signal you need to find bugs. Keep reporting broad, turn display off and logging on: errors are then recorded but never reach visitors.

saying these in an interview costs you the question

  • With no php.ini, PHP hides errors and logs them by default
  • Setting error_reporting to 0 is the safe production setting
  • log_errors and display_errors cannot both be On at once
  • If error_log is unset, PHP silently discards logged errors
  • Showing errors in production is fine as long as the site uses HTTPS
open as a page

How do you turn PHP warnings and notices into exceptions with set_error_handler and ErrorException?

level: middleimportance: must knowfreq 50%

basics

~20 s

Register a callback with set_error_handler() that throws new ErrorException($errstr, 0, $errno, $errfile, $errline). Warnings and notices then become catchable exceptions; the callback should first skip levels excluded by error_reporting(), and fatal or compile-time errors never reach it.

open as a page

In PHP, how do the E_* constants combine into an error_reporting level, and what does E_ALL & ~E_DEPRECATED report?

level: juniorimportance: should knowfreq 45%

basics

~20 s

Each E_* constant is a single bit, so levels combine with | and are removed with & ~. E_ALL & ~E_DEPRECATED, the php.ini-production value, reports every level except engine deprecations; E_ALL, the default since PHP 8.0, reports everything.

open as a page

In PHP 8, what does the @ error-control operator suppress, and how does it interact with fatal errors and custom error handlers?

level: middleimportance: should knowfreq 36%

basics

~20 s

@ hides the warnings, notices and deprecations one expression raises. Since PHP 8.0 it no longer hides fatal errors; it never stops exceptions; and a custom error handler still runs, seeing error_reporting() return 4437 rather than 0.

open as a page

In PHP, what does set_exception_handler() do, and what happens to the script after the handler has run?

level: middleimportance: should knowfreq 40%

basics

~20 s

set_exception_handler() registers a callback for any Throwable that no catch block handled. PHP unwinds the stack, runs pending finally blocks, calls the handler with the object and then ends the script; execution never resumes at the throw site.

open as a page

A PHP 8.5 booking site shows a blank white page in production and nothing appears in its error log — how do you track down the cause?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Check the status code, then the serving process's error settings and where its log really goes. Look for handlers that swallow failures, and capture fatal errors — which bypass set_error_handler — with a shutdown function reading error_get_last().

open as a page