5555(defn- expand-exception
5656 [^Throwable exception]
5757 (let [properties (bean exception)
58- cause (:cause properties)
5958 nil-property-keys (match-keys properties nil?)
6059 throwable-property-keys (match-keys properties #(.isInstance Throwable %))
6160 remove' #(remove %2 %1 )
168167
169168(def ^:dynamic *fonts*
170169 " 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})
179179
180180(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 ))))))
195230
196231(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))
199237 elements' (if frame-limit (take frame-limit elements) elements)
200238 formatter (c/format-columns [:right (c/max-value-length elements' :formatted-name )]
201239 " " (:source *fonts*)
217255 " Writes a formatted version of the exception to the writer. By default, writes to *out* and includes
218256 the stack trace, with no frame limit.
219257
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+
220278 When set, the frame limit is the number of stack frames to display; if non-nil, then some of the outer-most
221279 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.
223281
224282 Properties of exceptions will be output using Clojure's pretty-printer, honoring all of the normal vars used
225283 to configure pretty-printing; however, if *print-length* is left as its default (nil), the print length will be set to 10.
226284 This is to ensure that infinite lists do not cause endless output or other exceptions.
227285
228286 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 }}]
233295 (let [exception-font (:exception *fonts*)
234296 message-font (:message *fonts*)
235297 property-font (:property *fonts*)
261323 (str property-font k reset-font)
262324 (-> properties (get k) format-property-value)))))
263325 (if (:root e)
264- (write-stack-trace writer exception frame-limit)))))
326+ (write-stack-trace writer exception frame-limit frame-filter )))))
265327 (w/flush-writer writer)))
266328
267329(defn format-exception
268330 " 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