Skip to content

Commit 2cb5499

Browse files
authored
Merge pull request #129 from clj-commons/hls/20240911-tiny-improvements
Two small improvements to the clj-commons.format.exceptions namespace
2 parents ef8943c + 072acb7 commit 2cb5499

File tree

3 files changed

+56
-20
lines changed

3 files changed

+56
-20
lines changed

CHANGES.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 3.2.0 - UNRELEASED
2+
3+
Added `clj-commons.format.exceptions/default-frame-rules` with the defaults for `*default-frame-rules*`
4+
which makes it much easier to override the rules.
5+
6+
Added function `clj-commons.format.exceptions/format-stack-trace-element` which can be used to convert a Java
7+
StackTraceElement into demangled, readable string, using the same logic used by `format-exception.`
8+
19
## 3.1.1 - 22 Aug 2024
210

311
In a Clojure stack frame, repeated elements may be abbreviated; for example,

src/clj_commons/format/exceptions.clj

+34-19
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"Format and output exceptions in a pretty (structured, formatted) way."
33
(:require [clojure.pprint :as pp]
44
[clojure.set :as set]
5+
[clojure.string :as string]
56
[clojure.string :as str]
67
[clj-commons.ansi :refer [compose]]
78
[clj-commons.pretty-impl :refer [padding]])
@@ -102,25 +103,23 @@
102103
;; (seq m) is necessary because the source is via (bean), which returns an odd implementation of map
103104
(reduce (fn [result [k v]] (if (f v) (conj result k) result)) [] (seq m)))
104105

106+
(def ^{:added "3.2.0"} default-frame-rules
107+
"The set of rules that forms the default for [[*default-frame-rules*]], and the
108+
basis for [[*default-frame-filter*]], as a vector of vectors.
105109
106-
(def ^{:added "0.1.18"
107-
:dynamic true}
108-
*default-frame-rules*
109-
"The set of rules that forms the basis for [[*default-frame-filter*]], as a vector of vectors.
110+
Each rule is a vector of three values:
110111
111-
Each rule is a vector of three values:
112+
* A function that extracts the value from the stack frame map (typically, this is a keyword such
113+
as :package or :name). The value is converted to a string.
114+
* A string or regexp used for matching. Strings must match exactly.
115+
* A resulting frame visibility (:hide, :omit, :terminate, or :show).
112116
113-
* A function that extracts the value from the stack frame map (typically, this is a keyword such
114-
as :package or :name). The value is converted to a string.
115-
* A string or regexp used for matching. Strings must match exactly.
116-
* A resulting frame visibility (:hide, :omit, :terminate, or :show).
117+
The default rules:
117118
118-
The default rules:
119-
120-
* omit everything in `clojure.lang`, `java.lang.reflect`, and the function `clojure.core/apply`
121-
* hide everything in `sun.reflect`
122-
* terminate at `speclj.*`, `clojure.main/main*`, `clojure.main/repl/read-eval-print`, or `nrepl.middleware.interruptible-eval`
123-
"
119+
* omit everything in `clojure.lang`, `java.lang.reflect`, and the function `clojure.core/apply`
120+
* hide everything in `sun.reflect`
121+
* terminate at `speclj.*`, `clojure.main/main*`, `clojure.main/repl/read-eval-print`, or `nrepl.middleware.interruptible-eval`
122+
"
124123
[[:package "clojure.lang" :omit]
125124
[:package #"sun\.reflect.*" :hide]
126125
[:package "java.lang.reflect" :omit]
@@ -130,22 +129,29 @@
130129
[:name #"clojure\.main/repl/read-eval-print.*" :terminate]
131130
[:name #"clojure\.main/main.*" :terminate]])
132131

132+
(def ^{:added "0.1.18"
133+
:dynamic true}
134+
*default-frame-rules*
135+
"The set of rules that forms the basis for [[*default-frame-filter*]], as a vector of vectors,
136+
initialized from [[default-frame-rules]]."
137+
default-frame-rules)
138+
133139
(defn- apply-rule
134140
[frame [f match visibility :as rule]]
135141
(let [value (str (f frame))]
136142
(cond
137143
(string? match)
138-
(if (= match value) visibility)
144+
(when (= match value) visibility)
139145

140146
(instance? Pattern match)
141-
(if (re-matches match value) visibility)
147+
(when (re-matches match value) visibility)
142148

143149
:else
144150
(throw (ex-info "unexpected match type in rule"
145151
{:rule rule})))))
146152

147153
(defn *default-frame-filter*
148-
"Default stack frame filter used when printing REPL exceptions, driven by [[*default-frame-rules*]]."
154+
"Default stack frame filter used when printing REPL exceptions; default value is derived from [[*default-frame-rules*]]."
149155
{:added "0.1.16"
150156
:dynamic true}
151157
[frame]
@@ -738,7 +744,7 @@
738744
739745
Properties of exceptions will be output using Clojure's pretty-printer, but using
740746
this namespace's versions of [[*print-length*]] and [[*print-level*]], which default to
741-
10 and 2, respectively.
747+
10 and 5, respectively.
742748
743749
The `*fonts*` var contains a map from output element names (as :exception or :clojure-frame) to
744750
a font def used with [[compose]]; this allows easy customization of the output."
@@ -886,3 +892,12 @@
886892
exceptions stack-trace stack-trace-batch)
887893
(recur :start lines
888894
exceptions stack-trace stack-trace-batch)))))))
895+
896+
(defn format-stack-trace-element
897+
"Formats a stack trace element into a single string identifying the Java method or Clojure function being executed."
898+
{:added "3.2.0"}
899+
[^StackTraceElement e]
900+
(let [{:keys [class method names]} (transform-stack-trace-element current-dir-prefix (volatile! {}) e)]
901+
(if (empty? names)
902+
(str class "." method)
903+
(->> names counted-terms (map counted-frame-name) (string/join "/")))))

test/clj_commons/exception_test.clj

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
(ns clj-commons.exception-test
22
(:use clojure.test)
33
(:require [clojure.string :as str]
4+
[matcher-combinators.matchers :as m]
45
[clj-commons.ansi :refer [*color-enabled*]]
56
[clj-commons.pretty-impl :refer [csi]]
6-
[clj-commons.format.exceptions :refer [*fonts* parse-exception format-exception]]))
7+
[clj-commons.format.exceptions :as f :refer [*fonts* parse-exception format-exception]]))
78

89
(deftest write-exceptions
910
(testing "exception properties printing"
@@ -609,3 +610,15 @@ failed with ABC123"
609610
(testing "Does not fail with a nil ex-info map key"
610611
(is (re-find #"nil.*nil"
611612
(format-exception (ex-info "Error" {nil nil}))))))
613+
614+
(deftest format-stack-trace-element
615+
(let [frame-names (->> (Thread/currentThread)
616+
.getStackTrace
617+
seq
618+
(mapv f/format-stack-trace-element))]
619+
(is (match?
620+
;; A few sample Java and Clojure frame names
621+
(m/embeds #{"java.lang.Thread.getStackTrace"
622+
"clojure.core/apply"
623+
"clojure.test/run-tests"})
624+
(set frame-names)))))

0 commit comments

Comments
 (0)