Port of Python's IceCream to Clojure(Script).
(require '[icecream.icecream :as icecream :refer [ic]])
Works on functions, special forms and macros:
;; function
(ic (+ 1 1))
;; "ic| (+ 1 1): 2"
;; => 2
;; special form
(ic (if true :yes :no))
;; "ic| (if true :yes :no): :yes"
;; => :yes
;; macro
(ic (when true :yes))
;; "ic| (when true :yes): :yes"
;; => :yesSmartly outputs scalars directly:
(ic 1)
;; "ic| 1"
;; => 1
(ic :a)
;; "ic| :a"
;; => :a
(ic 'a)
;; "ic| 'a"
;; => a
(ic "hello")
;; "ic| "hello""
;; => "hello"
(ic nil)
;; "ic| nil"
;; => nil
Behavior can be altered through the use of dynamic variables:
;; disable
(binding [icecream/*enabled* false]
(ic 1))
;; => 1
;; custom output function
(require '[taoensso.timbre :as timbre
:refer [debug]])
(binding [icecream/*output-function* #(debug %)
;; NB: you typically want this one as well, explained later
icecream/*include-file-location-in-context* false]
(ic 1))
;; 21-07-07 20:01:03 homebase DEBUG [icecream-test.core:131] - ic| 1
;; => 1
;; custom prefix - constant
(binding [icecream/*prefix* "hello: "]
(ic 1))
;; "hello: 1"
;; => 1
;; custom prefix - function
(require '[tick.alpha.api :as t])
(binding [icecream/*prefix* #(str (inst-ms (t/now)) "| ")]
(ic 1))
;; "1619103609280| 1"
;; => 1
;; systematically include call context
(binding [icecream/*include-context* true]
(test 1))
;; "ic| form-init4003934083635828251.clj:6 in icecream-test.core/eval51124- 1
;; => 1
;; same but don't include file name / line number
(binding [icecream/*include-context* true
icecream/*include-file-location-in-context* false]
(test 1))
;; "ic| icecream-test.core/eval51124- 1
;; => 1
;; smartly include call context (only when inside function calls)
(binding [icecream/*include-context* :smart]
(ic 1))
;; "ic| 1"
;; => 1
(defn test [v]
(ic v))
(binding [icecream/*include-context* :smart]
(test 1))
;; "ic| core.clj:6 in icecream-test.core/test- 1
;; => 1
(binding [icecream/*include-context* false]
(test 1))
;; "ic| 1
;; => 1
;; when calling `ic` w/ no args, `*include-context*` is systematically considered true
(ic)
;; "ic| form-init4003934083635828251.clj:6 in icecream-test.core/eval51125
;; => nilIntrospection (when *include-context* is true or :smart) won't work in some cases. Typically in multi-methods or when calling dynamically created lambda functions.
You might want to take a look at the native tap API.
This blog post describe how it can be used for debugging purposes.
It allows binding several print destinations (akin to iceream's *output-function* config). This way you could both log in the REPL and forward say portal.
One notable difference is that tap> doesn't return the evaluated form value, but instead a boolean indicating if the action to output succeeded. As such it cannot be inserted as easily as other solutions (inside function calls, threading macros...).
Have also a look at the convenient pez/taplet that allows quickly tapping a whole let-binding vector.
spyscope provides several utilities similar to icecream but in the form of reader tags instead of macros.
Notably #spy/d ("details") is very close in behavior to ic, including context resolution.
It also provides #spy/t ("trace") that is an aggregated asynchronous version better suited for multi-threaded applications.
Its output function cannot be customized.
tupelo provides spyx that behaves very close to ic but for which the output function (println) can't be redefined.
It doesn't print caller info (*include-context* config). tupelo nethertheless provides fn-info and fn-info-caller (both Clojure only) that is very close in implementation to icecream's get-call-context.
hashp works just like spyscope's #spy/p but includes partial context (call location in file).