]> git.ipfire.org Git - thirdparty/starlette.git/commitdiff
feat(ServerErrorMiddleware): Frames have been reverted. Style improved. Collapse... 544/head
authormarcosschroh <schrohm@gmail.com>
Thu, 13 Jun 2019 22:57:54 +0000 (00:57 +0200)
committermarcosschroh <schrohm@gmail.com>
Mon, 17 Jun 2019 08:37:23 +0000 (10:37 +0200)
starlette/middleware/errors.py

index 54f5fd2aefc2e17c0238426f8784c206f56b0d89..0d153ac46c1653ee548ccb4b56514fedcd48f79f 100644 (file)
@@ -1,4 +1,5 @@
 import asyncio
+import inspect
 import traceback
 import typing
 
@@ -8,6 +9,9 @@ from starlette.responses import HTMLResponse, PlainTextResponse, Response
 from starlette.types import ASGIApp, Message, Receive, Scope, Send
 
 STYLES = """
+p {
+    color: #211c1c;
+}
 .traceback-container {
     border: 1px solid #038BB8;
 }
@@ -18,20 +22,53 @@ STYLES = """
     font-size: 20px;
     margin-top: 0px;
 }
-.traceback-content {
-    padding: 5px 0px 20px 20px;
-}
 .frame-line {
+    padding-left: 10px;
+}
+.center-line {
+    background-color: #038BB8;
+    color: #f9f6e1;
+    padding: 5px 0px 5px 5px;
+}
+.lineno {
+    margin-right: 5px;
+}
+.frame-filename {
     font-weight: unset;
-    padding: 10px 10px 10px 20px;
+    padding: 10px 10px 10px 0px;
     background-color: #E4F4FD;
-    margin-left: 10px;
     margin-right: 10px;
     font: #394D54;
     color: #191f21;
     font-size: 17px;
     border: 1px solid #c7dce8;
 }
+.collapse-btn {
+    float: right;
+    padding: 0px 5px 1px 5px;
+    border: solid 1px #96aebb;
+    cursor: pointer;
+}
+.collapsed {
+  display: none;
+}
+"""
+
+JS = """
+<script type="text/javascript">
+    function collapse(element){
+        const frameId = element.getAttribute("data-frame-id");
+        const frame = document.getElementById(frameId);
+
+        if (frame.classList.contains("collapsed")){
+            element.innerHTML = "&#8210;";
+            frame.classList.remove("collapsed");
+        } else {
+            element.innerHTML = "+";
+            frame.classList.add("collapsed");
+        }
+    }
+</script>
 """
 
 TEMPLATE = """
@@ -45,23 +82,36 @@ TEMPLATE = """
     <body>
         <h1>500 Server Error</h1>
         <h2>{error}</h2>
-        <div class='traceback-container'>
-            <p class='traceback-title'>Traceback</p>
-            <div class='traceback-content'>{exc_html}</div>
+        <div class="traceback-container">
+            <p class="traceback-title">Traceback</p>
+            <div>{exc_html}</div>
         </div>
+        {js}
     </body>
 </html>
 """
 
 FRAME_TEMPLATE = """
 <div>
-    File <span class='debug-filename'>`{frame_filename}`</span>,
+    <p class="frame-filename"><span class="debug-filename frame-line">File {frame_filename}</span>,
     line <i>{frame_lineno}</i>,
     in <b>{frame_name}</b>
-    <p class='frame-line'>{frame_line}</p>
+    <span class="collapse-btn" data-frame-id="{frame_filename}-{frame_lineno}" onclick="collapse(this)">&#8210;</span>
+    </p>
+    <div id="{frame_filename}-{frame_lineno}">{code_context}</div>
 </div>
 """
 
+LINE = """
+<p><span class="frame-line">
+<span class="lineno">{lineno}.</span> {line}</span></p>
+"""
+
+CENTER_LINE = """
+<p class="center-line"><span class="frame-line center-line">
+<span class="lineno">{lineno}.</span> {line}</span></p>
+"""
+
 
 class ServerErrorMiddleware:
     """
@@ -121,25 +171,47 @@ class ServerErrorMiddleware:
             # to optionally raise the error within the test case.
             raise exc from None
 
-    def generate_frame_html(self, frame: traceback.FrameSummary) -> str:
+    def format_line(
+        self, position: int, line: str, frame_lineno: int, center_lineno: int
+    ) -> str:
+        values = {
+            "line": line.replace(" ", "&nbsp"),
+            "lineno": frame_lineno + (position - center_lineno),
+        }
+
+        if position != center_lineno:
+            return LINE.format(**values)
+        return CENTER_LINE.format(**values)
+
+    def generate_frame_html(self, frame: inspect.FrameInfo, center_lineno: int) -> str:
+        code_context = "".join(
+            self.format_line(context_position, line, frame.lineno, center_lineno)
+            for context_position, line in enumerate(frame.code_context)
+        )
+
         values = {
             "frame_filename": frame.filename,
             "frame_lineno": frame.lineno,
-            "frame_name": frame.name,
-            "frame_line": frame.line,
+            "frame_name": frame.function,
+            "code_context": code_context,
         }
         return FRAME_TEMPLATE.format(**values)
 
-    def generate_html(self, exc: Exception) -> str:
+    def generate_html(self, exc: Exception, limit: int = 7) -> str:
         traceback_obj = traceback.TracebackException.from_exception(
             exc, capture_locals=True
         )
+        frames = inspect.getinnerframes(
+            traceback_obj.exc_traceback, limit  # type: ignore
+        )
+
+        center_lineno = int((limit - 1) / 2)
         exc_html = "".join(
-            self.generate_frame_html(frame) for frame in traceback_obj.stack
+            self.generate_frame_html(frame, center_lineno) for frame in reversed(frames)
         )
         error = f"{traceback_obj.exc_type.__name__}: {traceback_obj}"
 
-        return TEMPLATE.format(styles=STYLES, error=error, exc_html=exc_html)
+        return TEMPLATE.format(styles=STYLES, js=JS, error=error, exc_html=exc_html)
 
     def generate_plain_text(self, exc: Exception) -> str:
         return "".join(traceback.format_tb(exc.__traceback__))