Skip to content

Serve a plain-text traceback to non-HTML clients, linking to the interactive page #28

Description

@amol-

Goal

When the failing request does not accept HTML, respond with the plain-text traceback plus a URL that opens the interactive debugger for that same crash, instead of a 13 KB HTML page a JSON client cannot use.

Done criteria:

  • DebuggerCore decides the crash response format by asking whether the client accepts HTML, rather than enumerating API content types. HTML stays the default: a missing Accept, or one containing text/html, text/*, or */*, keeps today's interactive page. Only a client that explicitly accepts something else (application/json, application/xml, application/*+json, ...) gets text/plain.
  • The plain-text body is the existing traceback.plaintext, followed by a URL that reopens the crash in the interactive debugger, anchored at the frame that raised (#frame-<id>; the page already emits those anchors).
  • A new debugger endpoint re-renders a stored traceback by id, e.g. ?__debugger__=yes&cmd=traceback&tb=<traceback_id>&s=<secret>, reusing self.tracebacks and Traceback.render_full(). This is what makes the link possible: today the interactive page exists only inline in the crash response, so a crashed POST or XHR can never be reopened. Guard it with the session secret, consistent with cmd=source.
  • Accept parsing stays a hand-rolled substring test. backlash declares no runtime dependencies, so no parsing library may be introduced for this, and pathological headers such as text/html;q=0 are not worth handling.
  • Both transports inherit the behavior from the core; the WSGI and ASGI shells only pass the request's Accept value down.
  • Tests cover: HTML client unchanged, Accept: application/json gets text/plain with the link, missing Accept gets HTML, */* gets HTML, the re-render endpoint returns the interactive page for a stored id, an unknown or evicted id degrades cleanly, and a wrong secret is rejected.

One decision to record: whether the link is a relative, query-only URL (?__debugger__=yes&cmd=traceback&...), which needs no new plumbing and stays correct behind a reverse-proxy prefix, or an absolute path, which reads better in a terminal but requires the shells to pass the request path (SCRIPT_NAME + PATH_INFO, or root_path + path) into the core.

Non-goals: no JSON or XML rendering of the traceback, no honouring of X-Requested-With, no q-value ranking, and no change to the status code or to the post-response-started paths.

Why

The debugger performs no content negotiation at all: both shells hardcode text/html; charset=utf-8, so a crashing JSON endpoint answers 500 with an HTML document. A client calling response.json() gets a parse error on top of the original failure, and the traceback is only readable by digging the raw response out of browser devtools.

The interactive part is lost entirely in that case. The console UI cannot be driven from a devtools response pane, and re-issuing the request in the address bar to obtain a clickable page only works for GET. For a failing POST or XHR, the captured frames are unreachable in practice even though they are still held in memory.

A plain-text body makes the failure legible where developers actually see it, and the link restores access to the console for exactly the frames that already exist. It also gives Werkzeug-divergence value in the direction users notice: ASGI deployments are predominantly APIs, which is where the HTML-only response hurts most.

References

  • backlash/debugger.pyDebuggerCore.debugger_response() (where a cmd=traceback branch fits) and save_traceback() (populates self.tracebacks).
  • backlash/wsgi.py and backlash/asgi.py — the two crash paths that currently hardcode text/html.
  • backlash/tbtools.pyTraceback.plaintext, Traceback.render_full(), and the id="frame-<id>" anchors used for deep-linking.
  • django.views.debug — prior art for choosing the format from request.accepts('text/html').
  • Retention of the stored tracebacks the link depends on is bounded by Bound the number of tracebacks retained by the debugger #27.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions