Skip to content

[stacktrace] Add support for directly inspecting ex-data #930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* Bump `orchard` to [0.33.0](https://github.com/clojure-emacs/orchard/blob/master/CHANGELOG.md#0330-2025-04-08).
* [#929](https://github.com/clojure-emacs/cider-nrepl/pull/929): Migrate profiling middleware to orchard.profile.
* [#930](https://github.com/clojure-emacs/cider-nrepl/pull/930): Stacktrace: add support for directly inspecting ex-data

## 0.54.0 (2025-04-05)

Expand Down
3 changes: 2 additions & 1 deletion doc/modules/ROOT/pages/nrepl-api/ops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ Required parameters::


Optional parameters::
{blank}
* `:ex-data` When equal to "true", inspect ex-data of the exception instead of full exception.


Returns::
* `:status` "done", or "no-error" if ``analyze-last-stacktrace`` wasn't called beforehand (or the ``index`` was out of bounds).
Expand Down
1 change: 1 addition & 0 deletions src/cider/nrepl.clj
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@ those configured directories will be honored."
"inspect-last-exception" {:doc "Returns an Inspector response for the last exception that has been processed through `analyze-last-stacktrace` for the current nrepl session.
Assumes that `analyze-last-stacktrace` has been called first, returning \"no-error\" otherwise."
:requires {"index" "0 for inspecting the top-level exception, 1 for its ex-cause, 2 for its ex-cause's ex-cause, and so on."}
:optional {"ex-data" "When equal to \"true\", inspect ex-data of the exception instead of full exception."}
:returns {"status" "\"done\", or \"no-error\" if `analyze-last-stacktrace` wasn't called beforehand (or the `index` was out of bounds)."
"value" "A value, as produced by the Inspector middleware."}}
"stacktrace" {:doc "Return messages describing each cause and
Expand Down
28 changes: 18 additions & 10 deletions src/cider/nrepl/middleware/stacktrace.clj
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@

;; Analyze the last stacktrace

(def ^:dynamic *last-exception* nil)

(defn- analyze-last-stacktrace
"Analyze the last exception."
[{:keys [session] :as msg}]
(let [last-exception (@session #'*e)]
(swap! session assoc #'*last-exception* last-exception) ;; note that *e can change later, so this entry isn't redundant
;; We need to remember the analyzed exception separately because we need to
;; provide a way to inspect it while *e can change.
(alter-meta! session assoc ::analyzed-exception last-exception)
(send-analysis msg (stacktrace/analyze last-exception))))

(defn- handle-analyze-last-stacktrace-op
Expand All @@ -42,14 +42,22 @@
(handle-analyze-last-stacktrace-op msg)
(notify-client msg "The `stacktrace` op is deprecated, please use `analyze-last-stacktrace` instead." :warning))

(defn- get-last-exception-cause [{:keys [session index] :as msg}]
(when index
(let [last-exception (::analyzed-exception (meta session))
causes (when last-exception
(->> (iterate #(.getCause ^Throwable %) last-exception)
(take-while some?)))]
(nth causes index nil))))

(defn handle-inspect-last-exception-op [{:keys [session index transport] :as msg}]
(let [last-exception (@session #'*last-exception*)
causes (->> (iterate #(.getCause ^Throwable %) last-exception)
(take-while some?))
cause (when index
(nth causes index nil))]
(if cause
(t/send transport (middleware.inspect/inspect-reply* msg cause))
(let [inspect-ex-data? (= (:ex-data msg) "true")
cause (get-last-exception-cause msg)
object (if inspect-ex-data?
(ex-data cause)
cause)]
(if object
(t/send transport (middleware.inspect/inspect-reply* msg object))
(respond-to msg :status :no-error))
(respond-to msg :status :done)))

Expand Down