Postmortem: A tiny value-oriented debugging tool, powered by transducers

I’ve just published the first release of Postmortem, a tiny debugging library for Clojure(Script)!

Postmortem is heavily inspired by similar existing libraries like scope-capture and miracle.save, but focuses more on the ease of “postmortem debugging”, as its name suggests.

One notable feature is integration with transducers. Using transducers, you can freely specify when to log data and/or what part of data will be logged:

(require '[postmortem.core :as pm]
         '[postmortem.xforms :as xf])

(defn sum [n]
  (loop [i n sum 0]
    (pm/dump :sum (xf/take-last 5))
    (if (= i 0)
      sum
      (recur (dec i) (+ i sum)))))

(sum 100) ;=> 5050

(pm/log-for :sum)
;=> [{:n 100, :i 4, :sum 5040}
;    {:n 100, :i 3, :sum 5044}
;    {:n 100, :i 2, :sum 5047}
;    {:n 100, :i 1, :sum 5049}
;    {:n 100, :i 0, :sum 5050}]

Another interesting feature is instrumentation. Like clojure.spec’s instrumentation, you can enable/disable logging for a function without touching its code at all. Instrumentation is useful especially for debugging a recursive function (and transducer integration also gives even more power to the feature):

(require '[postmortem.instrument :as pi])

(defn broken-factorial [n]
  (cond (= n 0) 1
        (= n 7) (/ (broken-factorial (dec n)) 0) ;; <- BUG HERE!!
        :else (* n (broken-factorial (dec n)))))

(pi/instrument `broken-factorial
               {:xform (comp (xf/take-until :err) (xf/take-last 5))})

(broken-factorial 10)
;; Execution error (ArithmeticException) at user/broken-factorial.
;; Divide by zero

(pm/log-for `broken-factorial)
;=> [{:args (3), :ret 6}
;    {:args (4), :ret 24}
;    {:args (5), :ret 120}
;    {:args (6), :ret 720}
;    {:args (7), :err #error {:cause "Divide by zero" ...}}]

If you’re interested, give it a try! Any feedback is welcome. :wink:

4 Likes

I’ve been playing around with this and just wanted to say that adding instrumentation to a function without having to change the code is a great feature. I can easily imagine being able to debug chain of functions without much effort. I haven’t seen this in any of the other debugging tools.

For my simple use-case I found the ability to record all local variables of a function, with the dump function, really useful:

(defn stats
    [fname [height weight] ]
        (def i 10)
        (println fname "is" height)
        (pm/dump :stats)
    )

user=> (stats "Bob" [178 68])
"Bob is 178"

user=> (pm/log-for :stats)
[{:fname "Bob", :p__9309 [178 68], :vec__9310 [178 68], :height 178, :weight 68}]

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.