Skip to content

Commit

Permalink
Add render_backtrace() function
Browse files Browse the repository at this point in the history
  • Loading branch information
paulbalandan committed Jan 2, 2025
1 parent 4764fe3 commit a807ccc
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 56 deletions.
51 changes: 51 additions & 0 deletions system/Common.php
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,57 @@ function remove_invisible_characters(string $str, bool $urlEncoded = true): stri
}
}

if (! function_exists('render_backtrace')) {
/**
* Renders a backtrace in a nice string format.
*
* @param list<array{
* file?: string,
* line?: int,
* class?: string,
* type?: string,
* function: string,
* args?: list<mixed>
* }> $backtrace
*/
function render_backtrace(array $backtrace): string
{
$backtraces = [];

foreach ($backtrace as $index => $trace) {
$frame = $trace + ['file' => '[internal function]', 'line' => 0, '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 fn ($value): string => match (true) {
is_object($value) => sprintf('Object(%s)', $value::class),
is_array($value) => $value !== [] ? '[...]' : '[]',
$value === null => 'null',
is_resource($value) => sprintf('resource (%s)', get_resource_type($value)),
default => 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);
}
}

if (! function_exists('request')) {
/**
* Returns the shared Request.
Expand Down
43 changes: 3 additions & 40 deletions system/Debug/Exceptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public function exceptionHandler(Throwable $exception)
'routeInfo' => $routeInfo,
'exFile' => clean_path($exception->getFile()), // {file} refers to THIS file
'exLine' => $exception->getLine(), // {line} refers to THIS line
'trace' => self::renderBacktrace($exception->getTrace()),
'trace' => render_backtrace($exception->getTrace()),
]);

// Get the first exception.
Expand All @@ -149,7 +149,7 @@ public function exceptionHandler(Throwable $exception)
'message' => $prevException->getMessage(),
'exFile' => clean_path($prevException->getFile()), // {file} refers to THIS file
'exLine' => $prevException->getLine(), // {line} refers to THIS line
'trace' => self::renderBacktrace($prevException->getTrace()),
'trace' => render_backtrace($prevException->getTrace()),
]);
}
}
Expand Down Expand Up @@ -527,7 +527,7 @@ private function handleDeprecationError(string $message, ?string $file = null, ?
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
'trace' => self::renderBacktrace($trace),
'trace' => render_backtrace($trace),
]
);

Expand Down Expand Up @@ -646,41 +646,4 @@ public static function highlightFile(string $file, int $lineNumber, int $lines =

return '<pre><code>' . $out . '</code></pre>';
}

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 fn ($value): string => match (true) {
is_object($value) => sprintf('Object(%s)', $value::class),
is_array($value) => $value !== [] ? '[...]' : '[]',
$value === null => 'null',
is_resource($value) => sprintf('resource (%s)', get_resource_type($value)),
default => 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);
}
}
10 changes: 10 additions & 0 deletions tests/system/CommonFunctionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -805,4 +805,14 @@ public function testIsWindowsUsingMock(): void
$this->assertSame(str_contains(php_uname(), 'Windows'), is_windows());
$this->assertSame(defined('PHP_WINDOWS_VERSION_MAJOR'), is_windows());
}

public function testRenderBacktrace(): void
{
$trace = (new RuntimeException('Test exception'))->getTrace();
$renders = explode("\n", render_backtrace($trace));

foreach ($renders as $render) {
$this->assertMatchesRegularExpression('/^\s*\d* .+(?:\(\d+\))?: \S+(?:(?:\->|::)\S+)?\(.*\)$/', $render);
}
}
}
16 changes: 0 additions & 16 deletions tests/system/Debug/ExceptionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,22 +128,6 @@ public function testDetermineCodes(): void
$this->assertSame([500, EXIT_DATABASE], $determineCodes(new DatabaseException('This.')));
}

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
);
}
}

public function testMaskSensitiveData(): void
{
$maskSensitiveData = $this->getPrivateMethodInvoker($this->exception, 'maskSensitiveData');
Expand Down

0 comments on commit a807ccc

Please sign in to comment.