php-fpm: capture & logs exceptions
All checks were successful
php-fpm-build / build (7.4) (push) Successful in 4m53s
All checks were successful
php-fpm-build / build (7.4) (push) Successful in 4m53s
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,23 +1,80 @@
|
||||
<?php
|
||||
// force-debug.php
|
||||
// Optional debug bootstrap that can be toggled with FORCE_DEBUG_ERRORS=1.
|
||||
// Automatically prepended; guarantees fatals reach container logs and optionally surfaces verbose output.
|
||||
|
||||
$forceDebug = getenv('FORCE_DEBUG_ERRORS');
|
||||
if ($forceDebug === false) {
|
||||
return;
|
||||
$forceDebugRaw = getenv('FORCE_DEBUG_ERRORS');
|
||||
$forceDebugEnabled = false;
|
||||
if ($forceDebugRaw !== false) {
|
||||
$normalized = strtolower(trim($forceDebugRaw));
|
||||
$forceDebugEnabled = in_array($normalized, ['1', 'true', 'yes', 'on'], true);
|
||||
}
|
||||
|
||||
$normalized = strtolower(trim($forceDebug));
|
||||
$enabled = in_array($normalized, ['1', 'true', 'yes', 'on'], true);
|
||||
|
||||
if (!$enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('display_startup_errors', '1');
|
||||
// Ensure PHP always logs to stderr even if application code tampers with settings.
|
||||
ini_set('log_errors', '1');
|
||||
ini_set('error_log', '/proc/self/fd/2');
|
||||
|
||||
error_log('[force-debug] Verbose error reporting enabled via FORCE_DEBUG_ERRORS');
|
||||
// Helper to safely write to container stderr without relying on php.ini.
|
||||
$forceDebugLog = static function (string $message): void {
|
||||
static $stderr = null;
|
||||
if ($stderr === null) {
|
||||
$stderr = @fopen('php://stderr', 'ab');
|
||||
}
|
||||
if ($stderr) {
|
||||
@fwrite($stderr, $message . PHP_EOL);
|
||||
} else {
|
||||
// Fallback to PHP's error_log if stderr is unavailable for any reason.
|
||||
error_log($message);
|
||||
}
|
||||
};
|
||||
|
||||
// Capture uncaught exceptions so they can never disappear silently.
|
||||
set_exception_handler(static function (Throwable $throwable) use ($forceDebugLog, $forceDebugEnabled): void {
|
||||
$message = sprintf(
|
||||
'[force-debug] Uncaught %s: %s in %s:%d',
|
||||
get_class($throwable),
|
||||
$throwable->getMessage(),
|
||||
$throwable->getFile(),
|
||||
$throwable->getLine()
|
||||
);
|
||||
$forceDebugLog($message);
|
||||
$forceDebugLog($throwable->getTraceAsString());
|
||||
|
||||
if ($forceDebugEnabled) {
|
||||
http_response_code(500);
|
||||
echo '<pre>' . htmlspecialchars($message . "\n" . $throwable->getTraceAsString(), ENT_QUOTES, 'UTF-8') . '</pre>';
|
||||
}
|
||||
});
|
||||
|
||||
// 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 '<pre>' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</pre>';
|
||||
}
|
||||
});
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user