diff --git a/README.md b/README.md index 61160a0..94b7130 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ The CI workflow is configured to build with the repository root as the Docker bu ## Logging & debugging - - PHP-FPM is configured to stream its master/process worker logs and PHP error log to stderr, so `docker compose logs php-fpm` (or your platform equivalent) will always contain fatal errors. - - When you need full stack traces in the browser, set `FORCE_DEBUG_ERRORS=1` on the `php-fpm` service. The shipped `force-debug.php` bootstrap will notice the flag, turn on verbose error reporting, and emit a single log line indicating that debug mode is active. + - PHP-FPM streams master/worker logs plus PHP fatals to stderr, so `docker compose logs php-fpm` (or your platform equivalent) will always contain the messages you need for incident response. + - The auto-prepend bootstrap additionally installs shutdown/exception hooks that write uncaught throwables and fatal errors to stderr even if WordPress or a plugin tampers with `ini_set()`. + - When you need full stack traces in the browser, set `FORCE_DEBUG_ERRORS=1` on the `php-fpm` service. The bootstrap enables verbose output and logs a single notice so you remember to remove it later. - Remove or unset `FORCE_DEBUG_ERRORS` after troubleshooting so production responses stay clean. ## Local Testing & Development diff --git a/docker-compose.yml b/docker-compose.yml index 46f5122..fd97498 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: php-fpm: build: context: . - dockerfile: docker/7.4/Dockerfile + dockerfile: php-fpm/7.4/Dockerfile image: local/auvem-php:7.4 restart: unless-stopped volumes: diff --git a/shared/php-fpm/force-debug.php b/shared/php-fpm/force-debug.php index 46c8621..973e715 100644 --- a/shared/php-fpm/force-debug.php +++ b/shared/php-fpm/force-debug.php @@ -1,23 +1,80 @@ getMessage(), + $throwable->getFile(), + $throwable->getLine() + ); + $forceDebugLog($message); + $forceDebugLog($throwable->getTraceAsString()); + + if ($forceDebugEnabled) { + http_response_code(500); + echo '
' . htmlspecialchars($message . "\n" . $throwable->getTraceAsString(), ENT_QUOTES, 'UTF-8') . '
'; + } +}); + +// Capture fatal shutdown errors (E_ERROR, E_PARSE, etc.) and surface them to stderr. +register_shutdown_function(static function () use ($forceDebugLog, $forceDebugEnabled): void { + $error = error_get_last(); + if ($error === null) { + return; + } + + $fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR]; + if (!in_array($error['type'], $fatalTypes, true)) { + return; + } + + $message = sprintf( + '[force-debug] Fatal error (%s): %s in %s:%d', + $error['type'], + $error['message'], + $error['file'], + $error['line'] + ); + $forceDebugLog($message); + + if ($forceDebugEnabled) { + http_response_code(500); + echo '
' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '
'; + } +}); + +if ($forceDebugEnabled) { + error_reporting(E_ALL); + ini_set('display_errors', '1'); + ini_set('display_startup_errors', '1'); + $forceDebugLog('[force-debug] Verbose error reporting enabled via FORCE_DEBUG_ERRORS'); +}