Skip to content

Commit

Permalink
Buffer write-exception output (#79)
Browse files Browse the repository at this point in the history
* Buffer write-exception output

This should help prevent interleaving of output when multiple threads simultaneously
write exceptions to *out*.
  • Loading branch information
hlship committed Sep 13, 2022
1 parent 155926f commit 5afb656
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 31 deletions.
11 changes: 8 additions & 3 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.2 -- UNRELEASED

Output from `write-exception` is now buffered; this should reduce the
interleaving of exception output when multiple threads are writing
exceptions simultaneously.

[Closed Issues](https://github.com/AvisoNovate/pretty/issues?q=is%3Aclosed+milestone%3A1.2)

## 1.1.1 -- 15 Dec 20201

Prevent warnings when using with Clojure 1.11.
Expand Down Expand Up @@ -235,6 +243,3 @@ This can remove _significant_ clutter from the exception output, making it that
to identify the true cause of the exception.

[Closed issues](https://github.com/AvisoNovate/pretty/issues?q=milestone%3A0.1.11)



11 changes: 6 additions & 5 deletions docs/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ This is best explained by example; here's a SQLException wrapped inside two Runt

This is greatly improved in Clojure 1.10 over prior Clojure releases, but still quite minimal.

On a good day, the exception messages will include all the details you need to resolve the problem ... even though
Clojure encourages you to use the ``ex-info`` to create an exception,
which puts important data into properties of the exception, which are not normally printed.
On a good day, the exception messages will include all the details you need to resolve the problem, which
is strangely at odds with Clojure's ``ex-info`` function; ``ex-info` encourages you to
put useful information into the `ex-data` of the exception, yet Clojure doesn't print out this data.
``write-exceptions`` by contrast, does output the ``ex-data``.

Meanwhile, you will have to mentally scan and parse the above text explosion, to parse out file names and line numbers.

Expand All @@ -67,7 +68,7 @@ modified to use ``write-exception``.
:alt: Formatted Exception

As you can see, this lets you focus in on the exact cause and location of your problem.

.
``write-exception`` flips around the traditional order, providing a chronologically sequential view:

* The stack trace leading to the root exception comes first, and is ordered outermost frame to innermost frame.
Expand All @@ -81,7 +82,7 @@ or Java class and method, and the right columns presenting the file name and lin
The stack frames themselves are filtered to remove details that are not relevant.
This filtering is via an optional function, so you can define filters that make sense for your code.
For example, the default filter omits frames in the clojure.lang package (they are reduced to ellipses), and truncates the
stack trace when when it reaches clojure.main/repl/read-eval-print.
stack trace when when it reaches ``clojure.main/repl/read-eval-print``.

Repeating stack frames are also identified and reduced to a single line (that identifies the number of frames).
This allows your infinite loop that terminates with a StackOverflowException to be reported in just a few lines, not
Expand Down
1 change: 1 addition & 0 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
:plugins [[lein-codox "0.10.7"]]
:profiles {:1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]}
:1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]}
:1.11 {:dependencies [[org.clojure/clojure "1.11.1"]]}
:dev {:dependencies [[criterium "0.4.6"]
[com.stuartsierra/component "1.0.0"]
[com.walmartlabs/test-reporting "1.1"]]}
Expand Down
37 changes: 18 additions & 19 deletions src/io/aviso/exception.clj
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
nil)

(def ^:dynamic *fonts*
"Current set of fonts used in exception formatting"
"Current set of fonts used in exception formatting. This can be overridden to change colors, our bound to nil
to disable fonts. Further, the environment variable DISABLE_DEFAULT_PRETTY_FONTS, if non-nil, will default
this to nil."
(when-not (System/getenv "DISABLE_DEFAULT_PRETTY_FONTS")
default-fonts))

Expand Down Expand Up @@ -142,9 +144,7 @@
{:rule rule})))))

(defn *default-frame-filter*
"Default stack frame filter used when printing REPL exceptions. This will omit frames in the `clojure.lang`
and `java.lang.reflect` package, hide frames in the `sun.reflect` package,
and terminates the stack trace at the read-eval-print loop frame."
"Default stack frame filter used when printing REPL exceptions, driven by [[*default-frame-rules*]]."
{:added "0.1.16"
:dynamic true}
[frame]
Expand Down Expand Up @@ -525,9 +525,7 @@

(defn write-exception*
"Contains the main logic for [[write-exception]], which simply expands
the exception (via [[analyze-exception]]) before invoking this function.
This code was extracted so as to support [[parse-exception]]."
the exception (via [[analyze-exception]]) before invoking this function."
{:added "0.1.21"}
[exception-stack options]
(let [{show-properties? :properties
Expand Down Expand Up @@ -568,6 +566,14 @@
(if modern?
(write-exception-stack))))

(defn format-exception
"Formats an exception as a multi-line string using the same options as [[write-exception]]."
([exception]
(format-exception exception nil))
([exception options]
(with-out-str
(write-exception* (analyze-exception exception options) options))))

(defn write-exception
"Writes a formatted version of the exception to *out*. By default, includes
the stack trace, with no frame limit.
Expand All @@ -581,15 +587,15 @@
: If true (the default) then properties of exceptions will be output.
:frame-limit
: If non-nil, the number of stack frames to keep when outputing the stack trace
: 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 deepest to shallowest order.
in order from deepest to most shallow.
Modern output is more readable; the stack trace comes first and is reversed: shallowest frame to deepest.
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.
Expand Down Expand Up @@ -633,15 +639,8 @@
([exception]
(write-exception exception nil))
([exception options]
(write-exception* (analyze-exception exception options) options)))

(defn format-exception
"Formats an exception as a multi-line string using [[write-exception]]."
([exception]
(format-exception exception nil))
([exception options]
(with-out-str (write-exception exception options))))

(print (format-exception exception options))
(flush)))

(defn ^:private assemble-final-stack [exceptions stack-trace stack-trace-batch options]
(let [stack-trace' (-> (map (partial expand-stack-trace-element @current-dir-prefix)
Expand Down
9 changes: 5 additions & 4 deletions test/demo.clj
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,18 @@
(throw (make-ex-info))
(test-failure)

*clojure-version*

;; 11 Feb 2016 - 553 µs (14 µs std dev) - Clojure 1.8
;; 13 Sep 2021 - 401 µs (16 µs std dev) - Clojure 1.11.1

(let [out (io/writer "target/output.txt")
e (make-ex-info)]
(c/bench (print-exception out e)))
(let [e (make-ex-info)]
(c/bench (e/format-exception e)))

;; 11 Feb 2016 - 213 µs (4 µs std dev) - Clojure 1.8
;; 28 Sep 2018 - 237 µs (8 µs std dev) - Clojure 1.9

(let [e (make-ex-info)]
(c/bench (doseq [x (e/analyze-exception e nil)]
(-> x :stack-trace doall))))

)

0 comments on commit 5afb656

Please sign in to comment.