Browse Source

Add basic chained exception rendering to HTML error pages

Add basic support for chained exception rendering to HTML error pages.
This makes debugging exceptions that were wrapped or chained either in
framework or application code a bit easier to work with as you can more
directly traverse the stack trace.
Mark Story 3 years ago
parent
commit
b320c02cb7

+ 8 - 0
src/Error/Renderer/WebExceptionRenderer.php

@@ -248,10 +248,18 @@ class WebExceptionRenderer implements ExceptionRendererInterface
         }
         $response = $response->withStatus($code);
 
+        $exceptions = [$exception];
+        $previous = $exception->getPrevious();
+        while ($previous != null) {
+            $exceptions[] = $previous;
+            $previous = $previous->getPrevious();
+        }
+
         $viewVars = [
             'message' => $message,
             'url' => h($url),
             'error' => $exception,
+            'exceptions' => $exceptions,
             'code' => $code,
         ];
         $serialize = ['message', 'url', 'code'];

+ 52 - 46
templates/element/exception_stack_trace.php

@@ -17,58 +17,64 @@
  */
 use Cake\Error\Debugger;
 
-foreach ($trace as $i => $stack):
-    $excerpt = $params = [];
+foreach ($exceptions as $level => $exc):
+    $stackTrace = Debugger::formatTrace($exc->getTrace(), [
+        'format' => 'array',
+        'args' => true,
+    ]);
+    foreach ($stackTrace as $i => $stack):
+        $excerpt = $params = [];
 
-    $line = null;
-    if (isset($stack['file'], $stack['line']) && is_numeric($stack['line'])):
-        $line = $stack['line'];
-        $excerpt = Debugger::excerpt($stack['file'], $line, 4);
-    endif;
-
-    if (isset($stack['file'])):
-        $file = $stack['file'];
-    else:
-        $file = '[internal function]';
-    endif;
+        $line = null;
+        if (isset($stack['file'], $stack['line']) && is_numeric($stack['line'])):
+            $line = $stack['line'];
+            $excerpt = Debugger::excerpt($stack['file'], $line, 4);
+        endif;
 
-    if (isset($stack['function'])):
-        if (!empty($stack['args'])):
-            foreach ((array)$stack['args'] as $arg):
-                $params[] = Debugger::exportVar($arg, 4);
-            endforeach;
+        if (isset($stack['file'])):
+            $file = $stack['file'];
         else:
-            $params[] = 'No arguments';
+            $file = '[internal function]';
         endif;
-    endif;
-?>
-    <div id="stack-frame-<?= $i ?>" style="display:<?= $i === 0 ? 'block' : 'none'; ?>;" class="stack-details">
-        <div class="stack-frame-header">
-            <span class="stack-frame-file">
-                <?php if ($line !== null): ?>
-                    <?= $this->Html->link(Debugger::trimPath($file), Debugger::editorUrl($file, $line)); ?>
-                <?php else: ?>
-                    <?= h(Debugger::trimPath($file)); ?>
-                <?php endif; ?>
-            </span>
-            <a href="#" class="toggle-link stack-frame-args" data-target="stack-args-<?= $i ?>">Toggle Arguments</a>
-        </div>
 
-        <table class="code-excerpt" cellspacing="0" cellpadding="0">
-        <?php $lineno = isset($stack['line']) && is_numeric($stack['line']) ? $stack['line'] - 4 : 0 ?>
-        <?php foreach ($excerpt as $l => $line): ?>
-            <tr>
-                <td class="excerpt-number" data-number="<?= $lineno + $l ?>"></td>
-                <td class="excerpt-line"><?= $line ?></td>
-            </tr>
-        <?php endforeach; ?>
-        </table>
+        if (isset($stack['function'])):
+            if (!empty($stack['args'])):
+                foreach ((array)$stack['args'] as $arg):
+                    $params[] = Debugger::exportVar($arg, 4);
+                endforeach;
+            else:
+                $params[] = 'No arguments';
+            endif;
+        endif;
+    ?>
+        <div id="stack-frame-<?= $i ?>" style="display:<?= $i === 0 ? 'block' : 'none'; ?>;" class="stack-details">
+            <div class="stack-frame-header">
+                <span class="stack-frame-file">
+                    <?php if ($line !== null): ?>
+                        <?= $this->Html->link(Debugger::trimPath($file), Debugger::editorUrl($file, $line)); ?>
+                    <?php else: ?>
+                        <?= h(Debugger::trimPath($file)); ?>
+                    <?php endif; ?>
+                </span>
+                <a href="#" class="toggle-link stack-frame-args" data-target="stack-args-<?= $i ?>">Toggle Arguments</a>
+            </div>
 
-        <div id="stack-args-<?= $i ?>" class="cake-debug" style="display: none;">
-            <h4>Arguments</h4>
-            <?php foreach ($params as $param): ?>
-                <div class="cake-debug"><?= $param ?></div>
+            <table class="code-excerpt" cellspacing="0" cellpadding="0">
+            <?php $lineno = isset($stack['line']) && is_numeric($stack['line']) ? $stack['line'] - 4 : 0 ?>
+            <?php foreach ($excerpt as $l => $line): ?>
+                <tr>
+                    <td class="excerpt-number" data-number="<?= $lineno + $l ?>"></td>
+                    <td class="excerpt-line"><?= $line ?></td>
+                </tr>
             <?php endforeach; ?>
+            </table>
+
+            <div id="stack-args-<?= $i ?>" class="cake-debug" style="display: none;">
+                <h4>Arguments</h4>
+                <?php foreach ($params as $param): ?>
+                    <div class="cake-debug"><?= $param ?></div>
+                <?php endforeach; ?>
+            </div>
         </div>
-    </div>
+    <?php endforeach; ?>
 <?php endforeach; ?>

+ 31 - 21
templates/element/exception_stack_trace_nav.php

@@ -12,32 +12,42 @@
  * @since         3.0.0
  * @license       https://opensource.org/licenses/mit-license.php MIT License
  * @var array $trace
+ * @var \Throwable $error
+ * @var array<\Throwable> $exceptions
  */
 use Cake\Error\Debugger;
 ?>
 <a href="#" class="toggle-link toggle-vendor-frames">Toggle Vendor Stack Frames</a>
 
 <ul class="stack-trace">
-<?php foreach ($trace as $i => $stack): ?>
-    <?php
-    $class = isset($stack['file']) && strpos($stack['file'], APP) === false ? 'vendor-frame' : 'app-frame';
-    $class .= $i == 0 ? ' active' : '';
-    ?>
-    <li class="stack-frame <?= $class ?>">
-        <a href="#" data-target="stack-frame-<?= $i ?>">
-            <?php if (isset($stack['class'])): ?>
-                <span class="stack-function"><?= h($stack['class'] . $stack['type'] . $stack['function']) ?></span>
-            <?php elseif (isset($stack['function'])): ?>
-                <span class="stack-function"><?= h($stack['function']) ?></span>
-            <?php endif; ?>
-            <span class="stack-file">
-            <?php if (isset($stack['file'], $stack['line'])): ?>
-                <?= h(Debugger::trimPath($stack['file'])) ?>:<?= $stack['line'] ?>
-            <?php else: ?>
-                [internal function]
-            <?php endif ?>
-            </span>
-        </a>
-    </li>
+<?php foreach ($exceptions as $level => $exc): ?>
+    <?php if ($level > 0): ?>
+        <li class="stack-previous">
+            <span class="stack-function">Caused by</span> <?= h(get_class($exc)) ?>
+        </li>
+    <?php endif; ?>
+    <?php $stackTrace = Debugger::formatTrace($exc->getTrace(), ['format' => 'array']); ?>
+    <?php foreach ($stackTrace as $i => $stack): ?>
+        <?php
+        $class = isset($stack['file']) && strpos($stack['file'], APP) === false ? 'vendor-frame' : 'app-frame';
+        $class .= $i == 0 ? ' active' : '';
+        ?>
+        <li class="stack-frame <?= $class ?>">
+            <a href="#" data-target="stack-frame-<?= $i ?>">
+                <?php if (isset($stack['class'])): ?>
+                    <span class="stack-function"><?= h($stack['class'] . $stack['type'] . $stack['function']) ?></span>
+                <?php elseif (isset($stack['function'])): ?>
+                    <span class="stack-function"><?= h($stack['function']) ?></span>
+                <?php endif; ?>
+                <span class="stack-file">
+                <?php if (isset($stack['file'], $stack['line'])): ?>
+                    <?= h(Debugger::trimPath($stack['file'])) ?>:<?= $stack['line'] ?>
+                <?php else: ?>
+                    [internal function]
+                <?php endif ?>
+                </span>
+            </a>
+        </li>
+    <?php endforeach; ?>
 <?php endforeach; ?>
 </ul>

+ 3 - 0
templates/layout/dev_error.php

@@ -146,6 +146,9 @@ use Cake\Error\Debugger;
         margin: 0;
         padding: 0;
     }
+    .stack-previous {
+        margin: 24px 0 12px 8px;
+    }
     .stack-frame {
         background: #e5e5e5;
         padding: 10px;