55
55
(defn- expand-exception
56
56
[^Throwable exception]
57
57
(let [properties (bean exception)
58
- cause (:cause properties)
59
58
nil-property-keys (match-keys properties nil?)
60
59
throwable-property-keys (match-keys properties #(.isInstance Throwable %))
61
60
remove' #(remove %2 %1 )
168
167
169
168
(def ^:dynamic *fonts*
170
169
" ANSI fonts for different elements in the formatted exception report."
171
- {:exception bold-red-font
172
- :reset reset-font
173
- :message italic-font
174
- :property bold-font
175
- :source green-font
176
- :function-name bold-yellow-font
177
- :clojure-frame yellow-font
178
- :java-frame white-font})
170
+ {:exception bold-red-font
171
+ :reset reset-font
172
+ :message italic-font
173
+ :property bold-font
174
+ :source green-font
175
+ :function-name bold-yellow-font
176
+ :clojure-frame yellow-font
177
+ :java-frame white-font
178
+ :omiitted-frame white-font})
179
179
180
180
(defn- preformat-stack-frame
181
- [element]
182
- (let [names (:names element)]
183
- (if (-> element :names empty?)
184
- ; ; Case 1: names is empty, it's a Java frame
185
- (let [full-name (str (:class element) " ." (:method element))]
186
- (assoc element
187
- :formatted-name (str (:java-frame *fonts*) full-name (:reset *fonts*))))
188
- ; ; Case 2: it's a Clojure name
189
- (assoc element
190
- :formatted-name (str
191
- (:clojure-frame *fonts*)
192
- (->> names drop-last (str/join " /" ))
193
- " /"
194
- (:function-name *fonts*) (last names) (:reset *fonts*))))))
181
+ [frame]
182
+ (cond
183
+ (:omitted frame)
184
+ (assoc frame :formatted-name (str (:omitted-frame *fonts*) " ..." (:reset *fonts*))
185
+ :file " "
186
+ :line nil )
187
+
188
+ ; ; When :names is empty, it's a Java (not Clojure) frame
189
+ (-> frame :names empty?)
190
+ (let [full-name (str (:class frame) " ." (:method frame))
191
+ formatted-name (str (:java-frame *fonts*) full-name (:reset *fonts*))]
192
+ (assoc frame
193
+ :formatted-name formatted-name))
194
+
195
+ :else
196
+ (let [names (:names frame)
197
+ formatted-name (str
198
+ (:clojure-frame *fonts*)
199
+ (->> names drop-last (str/join " /" ))
200
+ " /"
201
+ (:function-name *fonts*) (last names) (:reset *fonts*))]
202
+ (assoc frame :formatted-name formatted-name))))
203
+
204
+ (defn- apply-frame-filter
205
+ [frame-filter frames]
206
+ (if (nil? frame-filter)
207
+ frames
208
+ (loop [result []
209
+ [frame & more-frames] frames
210
+ omitting false ]
211
+ (case (if frame (frame-filter frame) :terminate )
212
+
213
+ :terminate
214
+ result
215
+
216
+ :show
217
+ (recur (conj result frame)
218
+ more-frames
219
+ false )
220
+
221
+ :hide
222
+ (recur result more-frames omitting)
223
+
224
+ :omit
225
+ (if omitting
226
+ (recur result more-frames true )
227
+ (recur (conj result (assoc frame :omitted true ))
228
+ more-frames
229
+ true ))))))
195
230
196
231
(defn- write-stack-trace
197
- [writer exception frame-limit]
198
- (let [elements (->> exception expand-stack-trace (map preformat-stack-frame))
232
+ [writer exception frame-limit frame-filter]
233
+ (let [elements (->> exception
234
+ expand-stack-trace
235
+ (apply-frame-filter frame-filter)
236
+ (map preformat-stack-frame))
199
237
elements' (if frame-limit (take frame-limit elements) elements)
200
238
formatter (c/format-columns [:right (c/max-value-length elements' :formatted-name )]
201
239
" " (:source *fonts*)
217
255
" Writes a formatted version of the exception to the writer. By default, writes to *out* and includes
218
256
the stack trace, with no frame limit.
219
257
258
+ A frame filter may be specified; the frame filter is passed the stack frame maps; for each map
259
+ it may return one of :show, :hide, :omit, or :terminate. A stack frame map will have keys :file, :line, :class,
260
+ :package, :simple-class, :method, :name, and :names.
261
+
262
+ :file, :class, :package, :simple-class, :method, and :name are all strings. :line is an integer. :file and :line
263
+ are sometimes omitted.
264
+
265
+ :name is the Clojure name for the frame, or blank for a Java frame. :names is a vector of
266
+ strings representing first the namespace name, then the top-level function name, then nested function names
267
+ (which are often \" fn\" for anonymous functions). This is broken out primarily for rendering (so that the
268
+ last function name can be presented in bold), but may still be useful.
269
+
270
+ :show is the normal state; display the stack frame.
271
+ :hide prevents the frame from being displayed, as if it never existed.
272
+ :omit replaces the frame with a \" ...\" placeholder; multiple consecutive :omits will be collapsed to a single line.
273
+ Use :omit for \" uninteresting\" stack frames.
274
+ :terminate hides the frame AND all later frames.
275
+
276
+ The default is no filter; however the io.aviso.repl namespace does supply a standard filter.
277
+
220
278
When set, the frame limit is the number of stack frames to display; if non-nil, then some of the outer-most
221
279
stack frames may be omitted. It may be set to 0 to omit the stack trace entirely (but still display
222
- the exception stack).
280
+ the exception stack). The frame limit applies after the frame filter has been applied.
223
281
224
282
Properties of exceptions will be output using Clojure's pretty-printer, honoring all of the normal vars used
225
283
to configure pretty-printing; however, if *print-length* is left as its default (nil), the print length will be set to 10.
226
284
This is to ensure that infinite lists do not cause endless output or other exceptions.
227
285
228
286
The *fonts* var contains ANSI definitions for how fonts are displayed; bind it to nil to remove ANSI formatting entirely."
229
- ([exception] (write-exception *out* exception))
230
- ([writer exception & {show-properties? :properties
231
- frame-limit :frame-limit
232
- :or {show-properties? true }}]
287
+ ([exception]
288
+ (write-exception *out* exception))
289
+ ([writer exception]
290
+ (write-exception writer exception nil ))
291
+ ([writer exception {show-properties? :properties
292
+ frame-limit :frame-limit
293
+ frame-filter :filter
294
+ :or {show-properties? true }}]
233
295
(let [exception-font (:exception *fonts*)
234
296
message-font (:message *fonts*)
235
297
property-font (:property *fonts*)
261
323
(str property-font k reset-font)
262
324
(-> properties (get k) format-property-value)))))
263
325
(if (:root e)
264
- (write-stack-trace writer exception frame-limit)))))
326
+ (write-stack-trace writer exception frame-limit frame-filter )))))
265
327
(w/flush-writer writer)))
266
328
267
329
(defn format-exception
268
330
" Formats an exception as a multi-line string using write-exception."
269
- [exception & options]
270
- (apply w/into-string write-exception exception options))
331
+ ([exception]
332
+ (format-exception exception nil ))
333
+ ([exception options]
334
+ (w/into-string write-exception exception options)))
0 commit comments