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
|
## 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.
|
- 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.
|
||||||
- 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.
|
- 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.
|
- Remove or unset `FORCE_DEBUG_ERRORS` after troubleshooting so production responses stay clean.
|
||||||
|
|
||||||
## Local Testing & Development
|
## Local Testing & Development
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ services:
|
|||||||
php-fpm:
|
php-fpm:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: docker/7.4/Dockerfile
|
dockerfile: php-fpm/7.4/Dockerfile
|
||||||
image: local/auvem-php:7.4
|
image: local/auvem-php:7.4
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -1,23 +1,80 @@
|
|||||||
<?php
|
<?php
|
||||||
// force-debug.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');
|
$forceDebugRaw = getenv('FORCE_DEBUG_ERRORS');
|
||||||
if ($forceDebug === false) {
|
$forceDebugEnabled = false;
|
||||||
return;
|
if ($forceDebugRaw !== false) {
|
||||||
|
$normalized = strtolower(trim($forceDebugRaw));
|
||||||
|
$forceDebugEnabled = in_array($normalized, ['1', 'true', 'yes', 'on'], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$normalized = strtolower(trim($forceDebug));
|
// Ensure PHP always logs to stderr even if application code tampers with settings.
|
||||||
$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');
|
|
||||||
ini_set('log_errors', '1');
|
ini_set('log_errors', '1');
|
||||||
ini_set('error_log', '/proc/self/fd/2');
|
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