io.aviso.exception

Format and present exceptions in a pretty (structured, formatted) way.

*app-frame-names*

dynamic

Set of strings or regular expressions defining the application’s namespaces, which allows such namespaces to be highlighted in exception output.

*default-frame-filter*

dynamic

added in 0.1.16

(*default-frame-filter* frame)

Default stack frame filter used when printing REPL exceptions, driven by *default-frame-rules*.

*default-frame-rules*

dynamic

added in 0.1.18

The set of rules that forms the basis for *default-frame-filter*, as a vector or vectors.

Each rule is three values:

  • A function that extracts the value from the stack frame map (typically, this is a keyword such as :package or :name). The value is converted to a string.

  • A string or regexp used for matching.

  • A resulting frame visibility (:hide, :omit, :terminate, or :show).

The default rules:

  • omit everything in clojure.lang and java.lang.reflect.
  • hide everything in sun.reflect
  • terminate at speclj.*, clojure.main/repl/read-eval-print, or nrepl.middleware.interruptible-eval

*fonts*

dynamic

Current set of fonts used in exception formatting. This can be overridden to change colors, or bound to nil to disable fonts.

Further, the environment variable DISABLE_DEFAULT_PRETTY_FONTS, if non-nil, will default this to nil.

Starting in 1.3, ANSI fonts may be disabled at a lower level; see ansi-output-enabled?

*traditional*

dynamic

added in 0.1.15

If bound to true, then exceptions will be formatted the traditional way - the same as Java exceptions with the deepest stack frame first. By default, the stack trace is inverted, so that the deepest stack frames come last, mimicking chronological order.

analyze-exception

(analyze-exception e options)

Converts an exception into a seq of maps representing nested exceptions. The order reflects exception nesting; first exception is the most recently thrown, last is the deepest, or root, exception … the initial exception thrown in a chain of nested exceptions.

The options map is as defined by write-exception.

Each exception map contains:

:class-name String
name of the Java class for the exception
:message String
value of the exception’s message property (possibly nil)
:properties Map
map of properties to (optionally) present in the exception report
:stack-trace Vector
stack trace element maps, or nil. Only present in the root exception.

The :properties map does not include any properties that are assignable to type Throwable.

The first property that is assignable to type Throwable (not necessarily the rootCause property) will be used as the nested exception (for the next map in the sequence).

default-fonts

A default set of fonts for different elements in the formatted exception report.

demangle

(demangle s)

De-mangle a Java name back to a Clojure name by converting mangled sequences, such as “QMARK” back into simple characters.

exception-dispatch

multimethod

The pretty print dispatch function used when formatting exception output (specifically, when printing the properties of an exception). Normally, this is the same as the simple-dispatch (in clojure.pprint) but can be extended for specific cases:

(import com.stuartsierra.component.SystemMap)

(defmethod exception-dispatch SystemMap [system-map] (print "#<SystemMap>"))

This ensures that the SystemMap record, wherever it appears in the exception output, is represented as the string #<SystemMap>; normally it would print as a deeply nested set of maps.

See the io.aviso.component namespace for more complete example.

This same approach can be adapted to any class or type whose structure is problematic for presenting in the exception output, whether for size and complexity reasons, or due to security concerns.

expand-stack-trace

(expand-stack-trace exception)

Extracts the stack trace for an exception and returns a seq of expanded stack frame maps:

:file String
file name
:line Integer
line number as an integer, or nil
:class String
fully qualified Java class name
:package String
Java package name, or nil for root package
:simple-class String
simple name of Java class (without package prefix)
:method String
Java method name
:is-clojure?
true if this represents a Clojure function call, rather than a Java method invocation.
:name String
Fully qualified Clojure name (demangled from the Java class name), or the empty string for non-Clojure stack frames
:names seq of String
Clojure name split at slashes (empty for non-Clojure stack frames)

format-exception

(format-exception exception)(format-exception exception options)

Formats an exception as a multi-line string using the same options as write-exception.

parse-exception

added in 0.1.21

(parse-exception exception-text options)

Given a chunk of text for an exception report (as with .printStackTrace), attempts to piece together the same information provided by analyze-exception. The result is ready to pass to write-exception*.

This code does not attempt to recreate properties associated with the exceptions; in most exception’s cases, this is not necessarily written to the output. For clojure.lang.ExceptionInfo, it is hard to distinguish the message text from the printed exception map.

The options are used when processing the stack trace and may include the :filter and :frame-limit keys.

Returns a sequence of exception maps; the final map will include the :stack-trace key (a vector of stack trace element maps). The exception maps are ordered outermost to innermost (that final map is the root exception).

This should be considered experimental code; there are many cases where it may not work properly.

It will work quite poorly with exceptions whose message incorporates a nested exception’s .printStackTrace output. This happens too often with JDBC exceptions, for example.

write-exception

(write-exception exception)(write-exception exception options)

Writes a formatted version of the exception to out. By default, includes the stack trace, with no frame limit.

The options map may have the following keys:

:filter
The stack frame filter, which defaults to *default-stack-frame-filter*.
:properties
If true (the default) then properties of exceptions will be output.
:frame-limit
If non-nil, the number of stack frames to keep when outputting the stack trace of the deepest exception.

Output may be traditional or modern, as controlled by *traditional*. Traditional is the typical output order for Java: the stack of exceptions comes first (outermost to innermost) followed by the stack trace of the innermost exception, with the frames in order from deepest to most shallow.

Modern output is more readable; the stack trace comes first and is reversed: shallowest frame to most deep. Then the exception stack is output, from the root exception to the outermost exception. The modern output order is more readable, as it puts the most useful information together at the bottom, so that it is not necessary to scroll back to see, for example, where the exception occurred.

The default is modern.

The stack frame filter is passed the map detailing each stack frame in the stack trace, must return one of the following values:

:show
is the normal state; display the stack frame.
:hide
prevents the frame from being displayed, as if it never existed.
:omit
replaces the frame with a “…” placeholder; multiple consecutive :omits will be collapsed to a single line. Use :omit for “uninteresting” stack frames.
:terminate
hides the frame AND all later frames.

The default filter is *default-frame-filter*. An explicit filter of nil will display all stack frames.

Repeating lines are collapsed to a single line, with a repeat count. Typically, this is the result of an endless loop that terminates with a StackOverflowException.

When set, the frame limit is the number of stack frames to display; if non-nil, then some of the outermost stack frames may be omitted. It may be set to 0 to omit the stack trace entirely (but still display the exception stack). The frame limit is applied after the frame filter (which may hide or omit frames) and after repeating stack frames have been identified and coalesced … :frame-limit is really the number of output lines to present.

Properties of exceptions will be output using Clojure’s pretty-printer, honoring all of the normal vars used to configure pretty-printing; however, if *print-length* is left as its default (nil), the print length will be set to 10. This is to ensure that infinite lists do not cause endless output or other exceptions.

The *fonts* var contains ANSI definitions for how fonts are displayed; bind it to nil to remove ANSI formatting entirely. It can be also initialized to nil instead of the default set of fonts by setting environment variable DISABLE_DEFAULT_PRETTY_FONTS to any value.

write-exception*

added in 0.1.21

(write-exception* exception-stack options)

Contains the main logic for write-exception, which simply expands the exception (via analyze-exception) before invoking this function.