diff --git a/framework/core/src/Http/Server.php b/framework/core/src/Http/Server.php
index 88697f01d..e7faf3b17 100644
--- a/framework/core/src/Http/Server.php
+++ b/framework/core/src/Http/Server.php
@@ -9,6 +9,7 @@
namespace Flarum\Http;
+use Flarum\Foundation\ErrorHandling\LogReporter;
use Flarum\Foundation\SiteInterface;
use Laminas\Diactoros\Response;
use Laminas\Diactoros\ServerRequest;
@@ -16,6 +17,7 @@ use Laminas\Diactoros\ServerRequestFactory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Laminas\HttpHandlerRunner\RequestHandlerRunner;
use Laminas\Stratigility\Middleware\ErrorResponseGenerator;
+use Psr\Log\LoggerInterface;
use Throwable;
class Server
@@ -55,26 +57,70 @@ class Server
try {
return $this->site->bootApp()->getRequestHandler();
} catch (Throwable $e) {
- exit($this->formatBootException($e));
+ // Apply response code first so whatever happens, it's set before anything is printed
+ http_response_code(500);
+
+ try {
+ $this->cleanBootExceptionLog($e);
+ } catch (Throwable $e) {
+ // Ignore errors in logger. The important goal is to log the original error
+ }
+
+ $this->fallbackBootExceptionLog($e);
}
}
/**
- * Display the most relevant information about an early exception.
+ * Attempt to log the boot exception in a clean way and stop the script execution.
+ * This means looking for debug mode and/or our normal error logger.
+ * There is always a risk for this to fail,
+ * for example if the container bindings aren't present
+ * or if there is a filesystem error.
+ * @param Throwable $error
*/
- private function formatBootException(Throwable $error): string
+ private function cleanBootExceptionLog(Throwable $error)
{
- $message = $error->getMessage();
- $file = $error->getFile();
- $line = $error->getLine();
- $type = get_class($error);
+ if (app()->has('flarum.config') && app('flarum.config')->inDebugMode()) {
+ // If the application booted far enough for the config to be available, we will check for debug mode
+ // Since the config is loaded very early, it is very likely to be available from the container
+ $message = $error->getMessage();
+ $file = $error->getFile();
+ $line = $error->getLine();
+ $type = get_class($error);
- return <<
thrown in $file on line $line
$errorERROR; + exit(1); + } elseif (app()->has(LoggerInterface::class)) { + // If the application booted far enough for the logger to be available, we will log the error there + // Considering most boot errors are related to database or extensions, the logger should already be loaded + // We check for LoggerInterface binding because it's a constructor dependency of LogReporter, + // then instantiate LogReporter through the container for automatic dependency injection + app(LogReporter::class)->report($error); + + echo 'Flarum encountered a boot error. Details have been logged to the Flarum log file.'; + exit(1); + } + } + + /** + * If the clean logging doesn't work, then we have a last opportunity. + * Here we need to be extra careful not to include anything that might be sensitive on the page. + * @param Throwable $error + * @throws Throwable + */ + private function fallbackBootExceptionLog(Throwable $error) + { + echo 'Flarum encountered a boot error. Details have been logged to the system PHP log file.