[new] Extend trace!, spy! for easier catching

This commit is contained in:
Peter Taoussanis 2024-03-29 13:58:19 +01:00
parent dae36ef549
commit 5c85860b3e
5 changed files with 141 additions and 57 deletions

View file

@ -30,6 +30,9 @@ Tips:
- Execution of `form` arg may trigger additional (nested) signals. - Execution of `form` arg may trigger additional (nested) signals.
Each signal's `:parent` key will indicate its immediate parent. Each signal's `:parent` key will indicate its immediate parent.
- Can be useful to wrap with `catch->error!`:
(catch->error! ::error-id (spy! ...)).
---------------------------------------- ----------------------------------------
[1] See `help:signal-handling` docstring [1] See `help:signal-handling` docstring
[2] See `help:signal-content` docstring [2] See `help:signal-content` docstring

View file

@ -30,6 +30,9 @@ Tips:
- Execution of `form` arg may trigger additional (nested) signals. - Execution of `form` arg may trigger additional (nested) signals.
Each signal's `:parent` key will indicate its immediate parent. Each signal's `:parent` key will indicate its immediate parent.
- Can be useful to wrap with `catch->error!`:
(catch->error! ::error-id (trace! ...)).
- Default level is `:info`, not `:trace`! The name "trace" in "trace signal" - Default level is `:info`, not `:trace`! The name "trace" in "trace signal"
refers to the general action of tracing program flow rather than to the refers to the general action of tracing program flow rather than to the
common logging level of the same name. common logging level of the same name.

View file

@ -233,28 +233,6 @@
(comment (with-signal (throw (error! ::my-id (ex-info "MyEx" {}))))) (comment (with-signal (throw (error! ::my-id (ex-info "MyEx" {})))))
#?(:clj
(defmacro trace!
"[form] [id-or-opts form] => run result (value or throw)"
{:doc (impl/signal-docstring :trace!)
:arglists (impl/signal-arglists :trace!)}
[& args]
(let [opts (impl/signal-opts `trace! {:kind :trace, :level :info, :msg ::impl/spy} :run :id :asc args)]
(enc/keep-callsite `(impl/signal! ~opts)))))
(comment (with-signal (trace! ::my-id (+ 1 2))))
#?(:clj
(defmacro spy!
"[form] [level-or-opts form] => run result (value or throw)"
{:doc (impl/signal-docstring :spy!)
:arglists (impl/signal-arglists :spy!)}
[& args]
(let [opts (impl/signal-opts `spy! {:kind :spy, :level :info, :msg ::impl/spy} :run :level :asc args)]
(enc/keep-callsite `(impl/signal! ~opts)))))
(comment (with-signal (spy! :info (+ 1 2))))
#?(:clj #?(:clj
(defmacro catch->error! (defmacro catch->error!
"[form] [id-or-opts form] => run value or ?catch-val" "[form] [id-or-opts form] => run value or ?catch-val"
@ -262,7 +240,7 @@
:arglists (impl/signal-arglists :catch->error!)} :arglists (impl/signal-arglists :catch->error!)}
[& args] [& args]
(let [opts (impl/signal-opts `catch->error! {:kind :error, :level :error} ::__form :id :asc args) (let [opts (impl/signal-opts `catch->error! {:kind :error, :level :error} ::__form :id :asc args)
rethrow? (if (contains? opts :catch-val) false (get opts :rethrow?)) rethrow? (if (contains? opts :catch-val) false (get opts :rethrow? true))
catch-val (get opts :catch-val) catch-val (get opts :catch-val)
catch-sym (get opts :catch-sym '__caught-error) ; Undocumented catch-sym (get opts :catch-sym '__caught-error) ; Undocumented
form (get opts ::__form) form (get opts ::__form)
@ -270,7 +248,7 @@
(enc/keep-callsite (enc/keep-callsite
`(enc/try* ~form `(enc/try* ~form
(catch :any ~catch-sym (catch :all ~catch-sym
(impl/signal! ~(assoc opts :error catch-sym)) (impl/signal! ~(assoc opts :error catch-sym))
(if ~rethrow? (throw ~catch-sym) ~catch-val))))))) (if ~rethrow? (throw ~catch-sym) ~catch-val)))))))
@ -279,6 +257,54 @@
(with-signal (catch->error! { :msg_ ["Error:" __caught-error]} (/ 1 0))) (with-signal (catch->error! { :msg_ ["Error:" __caught-error]} (/ 1 0)))
(with-signal (catch->error! {:catch-sym my-err :msg_ ["Error:" my-err]} (/ 1 0)))) (with-signal (catch->error! {:catch-sym my-err :msg_ ["Error:" my-err]} (/ 1 0))))
#?(:clj
(defmacro trace!
"[form] [id-or-opts form] => run result (value or throw)"
{:doc (impl/signal-docstring :trace!)
:arglists (impl/signal-arglists :trace!)}
[& args]
(let [opts
(impl/signal-opts `trace!
{:location (enc/get-source &form &env) ; For catch-opts
:kind :trace, :level :info, :msg `impl/default-trace-msg}
:run :id :asc args)
;; :catch->error <id-or-opts> currently undocumented
[opts catch-opts] (impl/signal-catch-opts opts)]
(if catch-opts
(enc/keep-callsite `(catch->error! ~catch-opts (impl/signal! ~opts)))
(enc/keep-callsite `(impl/signal! ~opts))))))
(comment
(with-signal (trace! ::my-id (+ 1 2)))
(let [[_ [s1 s2]]
(with-signals
(trace! {:id :id1, :catch->error :id2}
(throw (ex-info "Ex1" {}))))]
[s2]))
#?(:clj
(defmacro spy!
"[form] [level-or-opts form] => run result (value or throw)"
{:doc (impl/signal-docstring :spy!)
:arglists (impl/signal-arglists :spy!)}
[& args]
(let [opts
(impl/signal-opts `spy!
{:location (enc/get-source &form &env) ; For catch-opts
:kind :spy, :level :info, :msg `impl/default-trace-msg}
:run :level :asc args)
;; :catch->error <id-or-opts> currently undocumented
[opts catch-opts] (impl/signal-catch-opts opts)]
(if catch-opts
(enc/keep-callsite `(catch->error! ~catch-opts (impl/signal! ~opts)))
(enc/keep-callsite `(impl/signal! ~opts))))))
(comment (with-signal :force (spy! :info (+ 1 2))))
#?(:clj #?(:clj
(defmacro uncaught->error! (defmacro uncaught->error!
"Uses `uncaught->handler!` so that `error!` will be called for "Uses `uncaught->handler!` so that `error!` will be called for

View file

@ -169,6 +169,16 @@
;; Leave user to delay-wrap when appropriate (document) ;; Leave user to delay-wrap when appropriate (document)
:else msg-form)))) :else msg-form))))
(defn default-trace-msg
[form value error nsecs]
(if error
(str form " !> " (enc/ex-type error))
(str form " => " value)))
(comment
(default-trace-msg "(+ 1 2)" 3 nil 12345)
(default-trace-msg "(+ 1 2)" nil (Exception. "Ex") 12345))
;;;; Tracing (optional flow tracking) ;;;; Tracing (optional flow tracking)
(enc/def* ^:dynamic *trace-parent* "?TraceParent" nil) (enc/def* ^:dynamic *trace-parent* "?TraceParent" nil)
@ -342,8 +352,8 @@
run-err (.-error run-result) run-err (.-error run-result)
run-val (.-value run-result) run-val (.-value run-result)
msg_ msg_
(if (enc/identical-kw? msg_ ::spy) (if (fn? msg_) ; Undocumented, handy for `trace!`/`spy!`, etc.
(delay (str run-form " => " (or run-err run-val))) (delay (msg_ run-form run-val run-err run-nsecs))
msg_)] msg_)]
(Signal. 1 inst uid, (Signal. 1 inst uid,
@ -468,11 +478,35 @@
arg-order))] arg-order))]
(if (or (= num-args 1) (map? extra-val-or-opts)) (if (or (= num-args 1) (map? extra-val-or-opts))
(let [opts extra-val-or-opts] (conj {:defaults defaults, main-key main-val } opts)) (let [opts extra-val-or-opts] (merge defaults {main-key main-val} opts))
(let [extra-val extra-val-or-opts] {:defaults defaults, main-key main-val, extra-key extra-val}))))) (let [extra-val extra-val-or-opts] (merge defaults {main-key main-val, extra-key extra-val}))))))
(comment (signal-opts `signal! {:level :info} :id :level :dsc [::my-id {:level :warn}])) (comment (signal-opts `signal! {:level :info} :id :level :dsc [::my-id {:level :warn}]))
#?(:clj
(defn signal-catch-opts
"For use within `trace!` and `spy!`, etc."
[main-opts]
(let [catch-id-or-opts (get main-opts :catch->error)
main-opts (dissoc main-opts :catch->error)
catch-opts
(when catch-id-or-opts
(let [base ; Inherit some opts from main
(enc/assoc-some {}
:location (get main-opts :location)
:id (get main-opts :id))]
(cond
(true? catch-id-or-opts) (do base)
(map? catch-id-or-opts) (conj base catch-id-or-opts)
:else (conj base {:id catch-id-or-opts}))))]
[main-opts catch-opts])))
(comment
(signal-catch-opts {:id :main-id, :catch->error true})
(signal-catch-opts {:id :main-id, :catch->error :error-id})
(signal-catch-opts {:id :main-id, :catch->error {:id :error-id}}))
;;;; Signal macro ;;;; Signal macro
#?(:clj #?(:clj

View file

@ -27,9 +27,9 @@
#?(:clj #?(:clj
(do (do
(defmacro ws [form] `(impl/-with-signals (fn [] ~form) {})) (defmacro ws [form] `(impl/-with-signals (fn [] ~form) {}))
(defmacro wsf [form] `(impl/-with-signals (fn [] ~form) {:force-msg? true})) (defmacro wsf [form] `(impl/-with-signals (fn [] ~form) {:force-msg? true}))
(defmacro wst [form] `(impl/-with-signals (fn [] ~form) {:trap-errors? true})) (defmacro wst [form] `(impl/-with-signals (fn [] ~form) {:force-msg? true, :trap-errors? true}))
(defmacro ws1 [form] `(let [[_# [s1#]] (impl/-with-signals (fn [] ~form) {:force-msg? true})] s1#)))) (defmacro ws1 [form] `(let [[_# [s1#]] (impl/-with-signals (fn [] ~form) {:force-msg? true})] s1#))))
(def ^:dynamic *dynamic-var* nil) (def ^:dynamic *dynamic-var* nil)
@ -430,13 +430,17 @@
(deftest _common-signals (deftest _common-signals
[#?(:clj [#?(:clj
(testing "signal-opts" (testing "signal-opts"
[(is (= (impl/signal-opts `signal! {:level :info} :id :level :dsc [::my-id ]) {:defaults {:level :info}, :id ::my-id})) [(is (= (impl/signal-opts `signal! {:level :info} :id :level :dsc [::my-id ]) {:level :info, :id ::my-id}))
(is (= (impl/signal-opts `signal! {:level :info} :id :level :dsc [::my-id :warn ]) {:defaults {:level :info}, :id ::my-id, :level :warn})) (is (= (impl/signal-opts `signal! {:level :info} :id :level :dsc [::my-id :warn ]) {:level :warn, :id ::my-id}))
(is (= (impl/signal-opts `signal! {:level :info} :id :level :dsc [::my-id {:level :warn}]) {:defaults {:level :info}, :id ::my-id, :level :warn})) (is (= (impl/signal-opts `signal! {:level :info} :id :level :dsc [::my-id {:level :warn}]) {:level :warn, :id ::my-id}))
(is (= (impl/signal-opts `signal! {:level :info} :id :level :asc [ ::my-id]) {:defaults {:level :info}, :id ::my-id})) (is (= (impl/signal-opts `signal! {:level :info} :id :level :asc [ ::my-id]) {:level :info, :id ::my-id}))
(is (= (impl/signal-opts `signal! {:level :info} :id :level :asc [:warn ::my-id]) {:defaults {:level :info}, :id ::my-id, :level :warn})) (is (= (impl/signal-opts `signal! {:level :info} :id :level :asc [:warn ::my-id]) {:level :warn, :id ::my-id}))
(is (= (impl/signal-opts `signal! {:level :info} :id :level :asc [{:level :warn} ::my-id]) {:defaults {:level :info}, :id ::my-id, :level :warn}))])) (is (= (impl/signal-opts `signal! {:level :info} :id :level :asc [{:level :warn} ::my-id]) {:level :warn, :id ::my-id}))
(is (= (impl/signal-catch-opts {:id :main-id, :location {:ns "ns"}, :catch->error true}) [{:id :main-id, :location {:ns "ns"}} {:location {:ns "ns"}, :id :main-id}]))
(is (= (impl/signal-catch-opts {:id :main-id, :location {:ns "ns"}, :catch->error :error-id}) [{:id :main-id, :location {:ns "ns"}} {:location {:ns "ns"}, :id :error-id}]))
(is (= (impl/signal-catch-opts {:id :main-id, :location {:ns "ns"}, :catch->error {:id :error-id}}) [{:id :main-id, :location {:ns "ns"}} {:location {:ns "ns"}, :id :error-id}]))]))
(testing "event!" ; id + ?level => allowed? (testing "event!" ; id + ?level => allowed?
[(let [[rv [sv]] (ws (tel/event! :id1 ))] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/ex, :level :info, :id :id1}))]) [(let [[rv [sv]] (ws (tel/event! :id1 ))] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/ex, :level :info, :id :id1}))])
@ -456,37 +460,51 @@
(let [[rv [sv]] (ws (tel/log! {:level :warn} "msg"))] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/ex, :msg_ "msg", :level :warn}))]) (let [[rv [sv]] (ws (tel/log! {:level :warn} "msg"))] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/ex, :msg_ "msg", :level :warn}))])
(let [[rv [sv]] (ws (tel/log! {:allow? false} "msg"))] [(is (= rv nil)) (is (nil? sv))])]) (let [[rv [sv]] (ws (tel/log! {:allow? false} "msg"))] [(is (= rv nil)) (is (nil? sv))])])
(testing "catch->error!" ; form + ?id => run value or ?return
[(let [[[rv re] [sv]] (wst (tel/catch->error! (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
(let [[[rv re] [sv]] (wst (tel/catch->error! (ex1!)))] [(is (= re ex1)) (is (sm? sv {:kind :error, :line :submap/ex, :level :error, :error ex1-pred, :id nil}))])
(let [[[rv re] [sv]] (wst (tel/catch->error! :id1 (ex1!)))] [(is (= re ex1)) (is (sm? sv {:kind :error, :line :submap/ex, :level :error, :error ex1-pred, :id :id1}))])
(let [[[rv re] [sv]] (wst (tel/catch->error! {:id :id1} (ex1!)))] [(is (= re ex1)) (is (sm? sv {:kind :error, :line :submap/ex, :level :error, :error ex1-pred, :id :id1}))])
(let [[[rv re] [sv]] (wst (tel/catch->error! {:rethrow? false} (ex1!)))] [(is (= re nil)) (is (sm? sv {:kind :error, :line :submap/ex, :level :error, :error ex1-pred, :id nil}))])
(let [[[rv re] [sv]] (wst (tel/catch->error! {:catch-val :foo} (ex1!)))] [(is (= rv :foo)) (is (sm? sv {:kind :error, :line :submap/ex, :level :error, :error ex1-pred, :id nil}))])
(let [[[rv re] [sv]] (wst (tel/catch->error! {:catch-val :foo} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
(let [[[rv re] [sv]] (wst (tel/catch->error! {:catch-val :foo ; Overrides `:rethrow?`
:rethrow? true} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
(let [[rv [sv]] (ws (tel/catch->error! {:catch-val nil
:catch-sym my-err
:data {:my-err my-err}} (ex1!)))]
[(is (= rv nil)) (is (sm? sv {:kind :error, :data {:my-err ex1-pred}}))])])
(testing "trace!" ; run + ?id => run result (value or throw) (testing "trace!" ; run + ?id => run result (value or throw)
[(let [[rv [sv]] (wsf (tel/trace! (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/ex, :level :info, :id nil, :msg_ "(+ 1 2) => 3"}))]) [(let [[rv [sv]] (wsf (tel/trace! (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/ex, :level :info, :id nil, :msg_ "(+ 1 2) => 3"}))])
(let [[rv [sv]] (ws (tel/trace! {:msg nil} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/ex, :level :info, :id nil, :msg_ nil}))]) (let [[rv [sv]] (ws (tel/trace! {:msg nil} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/ex, :level :info, :id nil, :msg_ nil}))])
(let [[rv [sv]] (ws (tel/trace! :id1 (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/ex, :level :info, :id :id1}))]) (let [[rv [sv]] (ws (tel/trace! :id1 (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/ex, :level :info, :id :id1}))])
(let [[rv [sv]] (ws (tel/trace! {:id :id1} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/ex, :level :info, :id :id1}))]) (let [[rv [sv]] (ws (tel/trace! {:id :id1} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/ex, :level :info, :id :id1}))])
(let [[[_ re] [sv]] (wst (tel/trace! :id1 (ex1!)))] [(is (= re ex1)) (is (sm? sv {:kind :trace, :line :submap/ex, :level :info, :id :id1, :error ex1-pred}))]) (let [[[_ re] [sv]] (wst (tel/trace! :id1 (ex1!)))] [(is (= re ex1)) (is (sm? sv {:kind :trace, :line :submap/ex, :level :info, :id :id1, :error ex1-pred,
(let [[rv [sv]] (ws (tel/trace! {:allow? false} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])]) :msg_ #?(:clj "(ex1!) !> clojure.lang.ExceptionInfo"
:cljs "(ex1!) !> cljs.core/ExceptionInfo")}))])
(let [[rv [sv]] (ws (tel/trace! {:allow? false} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
(let [[[_ ] [sv1 sv2]]
(wst (tel/trace! {:id :id1, :catch->error :id2} (ex1!)))]
[(is (sm? sv1 {:kind :trace, :line :submap/ex, :level :info, :id :id1}))
(is (sm? sv2 {:kind :error, :line :submap/ex, :level :error, :id :id2}))
(is (= (:location sv1) (:location sv2)) "Error inherits exact same location")])])
(testing "spy" ; run + ?level => run result (value or throw) (testing "spy" ; run + ?level => run result (value or throw)
[(let [[rv [sv]] (wsf (tel/spy! (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/ex, :level :info, :msg_ "(+ 1 2) => 3"}))]) [(let [[rv [sv]] (wsf (tel/spy! (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/ex, :level :info, :msg_ "(+ 1 2) => 3"}))])
(let [[rv [sv]] (wsf (tel/spy! {:msg nil} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/ex, :level :info, :msg_ nil}))]) (let [[rv [sv]] (wsf (tel/spy! {:msg nil} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/ex, :level :info, :msg_ nil}))])
(let [[rv [sv]] (ws (tel/spy! :warn (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/ex, :level :warn}))]) (let [[rv [sv]] (ws (tel/spy! :warn (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/ex, :level :warn}))])
(let [[rv [sv]] (ws (tel/spy! {:level :warn} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/ex, :level :warn}))]) (let [[rv [sv]] (ws (tel/spy! {:level :warn} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/ex, :level :warn}))])
(let [[[_ re] [sv]] (wst (tel/spy! :warn (ex1!)))] [(is (= re ex1)) (is (sm? sv {:kind :spy, :line :submap/ex, :level :warn, :error ex1-pred}))]) (let [[[_ re] [sv]] (wst (tel/spy! :warn (ex1!)))] [(is (= re ex1)) (is (sm? sv {:kind :spy, :line :submap/ex, :level :warn, :error ex1-pred,
(let [[rv [sv]] (ws (tel/spy! {:allow? false} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])]) :msg_ #?(:clj "(ex1!) !> clojure.lang.ExceptionInfo"
:cljs "(ex1!) !> cljs.core/ExceptionInfo")}))])
(testing "catch->error!" ; form + ?id => run value or ?return (let [[rv [sv]] (ws (tel/spy! {:allow? false} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
[(let [[rv [sv]] (ws (tel/catch->error! (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))]) (let [[[_ ] [sv1 sv2]]
(let [[rv [sv]] (ws (tel/catch->error! (ex1!)))] [(is (= rv nil)) (is (sm? sv {:kind :error, :line :submap/ex, :level :error, :error ex1-pred, :id nil}))]) (wst (tel/spy! {:id :id1, :catch->error :id2} (ex1!)))]
(let [[rv [sv]] (ws (tel/catch->error! :id1 (ex1!)))] [(is (= rv nil)) (is (sm? sv {:kind :error, :line :submap/ex, :level :error, :error ex1-pred, :id :id1}))]) [(is (sm? sv1 {:kind :spy, :line :submap/ex, :level :info, :id :id1}))
(let [[rv [sv]] (ws (tel/catch->error! {:id :id1} (ex1!)))] [(is (= rv nil)) (is (sm? sv {:kind :error, :line :submap/ex, :level :error, :error ex1-pred, :id :id1}))]) (is (sm? sv2 {:kind :error, :line :submap/ex, :level :error, :id :id2}))
(let [[[_ re] [sv]] (wst (tel/catch->error! {:rethrow? true} (ex1!)))] [(is (= re ex1)) (is (sm? sv {:kind :error, :line :submap/ex, :level :error, :error ex1-pred, :id nil}))]) (is (= (:location sv1) (:location sv2)) "Error inherits exact same location")])])
(let [[rv [sv]] (ws (tel/catch->error! {:catch-val :foo} (ex1!)))] [(is (= rv :foo)) (is (sm? sv {:kind :error, :line :submap/ex, :level :error, :error ex1-pred, :id nil}))])
(let [[rv [sv]] (ws (tel/catch->error! {:catch-val :foo} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
(let [[rv [sv]] (ws (tel/catch->error! {:catch-val :foo ; Overrides `:rethrow?`
:rethrow? true} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
(let [[rv [sv]] (ws (tel/catch->error! {:catch-val nil
:catch-sym my-err
:data {:my-err my-err}} (ex1!)))]
[(is (= rv nil)) (is (sm? sv {:kind :error, :data {:my-err ex1-pred}}))])])
#?(:clj #?(:clj
(testing "uncaught->error!" (testing "uncaught->error!"