diff --git a/system/Debug/Exceptions.php b/system/Debug/Exceptions.php index 0d96d7c087ca..879dab718a16 100644 --- a/system/Debug/Exceptions.php +++ b/system/Debug/Exceptions.php @@ -102,8 +102,11 @@ public function exceptionHandler(Throwable $exception) [$statusCode, $exitCode] = $this->determineCodes($exception); if ($this->config->log === true && ! in_array($statusCode, $this->config->ignoreCodes, true)) { - log_message('critical', $exception->getMessage() . "\n{trace}", [ - 'trace' => $exception->getTraceAsString(), + log_message('critical', "{message}\nin {exFile} on line {exLine}.\n{trace}", [ + 'message' => $exception->getMessage(), + 'exFile' => clean_path($exception->getFile()), // {file} refers to THIS file + 'exLine' => $exception->getLine(), // {line} refers to THIS line + 'trace' => self::renderBacktrace($exception->getTrace()), ]); } @@ -434,4 +437,55 @@ public static function highlightFile(string $file, int $lineNumber, int $lines = return '
' . $out . '';
}
+
+ private static function renderBacktrace(array $backtrace): string
+ {
+ $backtraces = [];
+
+ foreach ($backtrace as $index => $trace) {
+ $frame = $trace + ['file' => '[internal function]', 'line' => '', 'class' => '', 'type' => '', 'args' => []];
+
+ if ($frame['file'] !== '[internal function]') {
+ $frame['file'] = sprintf('%s(%s)', $frame['file'], $frame['line']);
+ }
+
+ unset($frame['line']);
+ $idx = $index;
+ $idx = str_pad((string) ++$idx, 2, ' ', STR_PAD_LEFT);
+
+ $args = implode(', ', array_map(static function ($value): string {
+ switch (true) {
+ case is_object($value):
+ return sprintf('Object(%s)', get_class($value));
+
+ case is_array($value):
+ return $value !== [] ? '[...]' : '[]';
+
+ case $value === null:
+ return 'null';
+
+ case is_resource($value):
+ return sprintf('resource (%s)', get_resource_type($value));
+
+ case is_string($value):
+ return var_export(clean_path($value), true);
+
+ default:
+ return var_export($value, true);
+ }
+ }, $frame['args']));
+
+ $backtraces[] = sprintf(
+ '%s %s: %s%s%s(%s)',
+ $idx,
+ clean_path($frame['file']),
+ $frame['class'],
+ $frame['type'],
+ $frame['function'],
+ $args
+ );
+ }
+
+ return implode("\n", $backtraces);
+ }
}
diff --git a/tests/system/Debug/ExceptionsTest.php b/tests/system/Debug/ExceptionsTest.php
index 247629933c69..8bd4f01e0925 100644
--- a/tests/system/Debug/ExceptionsTest.php
+++ b/tests/system/Debug/ExceptionsTest.php
@@ -61,4 +61,20 @@ public function testDetermineCodes(): void
$this->assertSame([500, 1], $determineCodes(new RuntimeException('That.', 600)));
$this->assertSame([404, 1], $determineCodes(new RuntimeException('There.', 404)));
}
+
+ public function testRenderBacktrace(): void
+ {
+ $renderer = self::getPrivateMethodInvoker(Exceptions::class, 'renderBacktrace');
+ $exception = new RuntimeException('This.');
+
+ $renderedBacktrace = $renderer($exception->getTrace());
+ $renderedBacktrace = explode("\n", $renderedBacktrace);
+
+ foreach ($renderedBacktrace as $trace) {
+ $this->assertMatchesRegularExpression(
+ '/^\s*\d* .+(?:\(\d+\))?: \S+(?:(?:\->|::)\S+)?\(.*\)$/',
+ $trace
+ );
+ }
+ }
}
diff --git a/user_guide_src/source/changelogs/v4.2.0.rst b/user_guide_src/source/changelogs/v4.2.0.rst
index abb5e9dc3c01..cc1561a98949 100644
--- a/user_guide_src/source/changelogs/v4.2.0.rst
+++ b/user_guide_src/source/changelogs/v4.2.0.rst
@@ -29,6 +29,8 @@ Enhancements
- Added new OCI8 driver for database.
- It can access Oracle Database and supports SQL and PL/SQL statements.
- The ``spark routes`` command now shows closure routes, auto routtes, and filters. See :ref:`URI Routing