[new] Add clean-signal-fn util

This commit is contained in:
Peter Taoussanis 2024-09-13 17:20:36 +02:00
parent d12b0b145b
commit be55f44a87
3 changed files with 99 additions and 77 deletions

View file

@ -71,6 +71,7 @@
#?(:clj impl/signal-allowed?)
;; Utils
utils/clean-signal-fn
utils/format-signal-fn
utils/pr-signal-fn
utils/error-signal?)

View file

@ -656,6 +656,75 @@
((signal-content-fn) (tel/with-signal (tel/event! ::ev-id)))
((signal-content-fn) (tel/with-signal (tel/event! ::ev-id {:data {:k1 "v1"}}))))
(defn clean-signal-fn
"Experimental, subject to change.
Returns a (fn clean [signal]) that:
- Takes a Telemere signal (map).
- Returns a minimal signal (map) ready for printing, etc.
Signals are optimized for cheap creation and easy handling, so tend to be
verbose and may contain things like nil values and duplicated content.
This util efficiently cleans signals of such noise, helping reduce
storage/transmission size, and making key info easier to see.
Options:
`:incl-nils?` - Include signal's keys with nil values? (default false)
`:incl-kvs?` - Include signal's app-level root kvs? (default false)
`:incl-keys` - Subset of signal keys to retain from those otherwise
excluded by default: #{:location :kvs :file :host :thread}"
([] (clean-signal-fn nil))
([{:keys [incl-kvs? incl-nils? incl-keys] :as opts}]
(let [assoc!*
(if-not incl-nils?
(fn [m k v] (if (nil? v) m (assoc! m k v))) ; As `remove-signal-nils`
(do assoc!))
incl-location? (contains? incl-keys :location)
incl-kvs-key? (contains? incl-keys :kvs)
incl-file? (contains? incl-keys :file)
incl-host? (contains? incl-keys :host)
incl-thread? (contains? incl-keys :thread)]
(fn clean-signal [signal]
(when (map? signal)
(persistent!
(reduce-kv
(fn [m k v]
(enc/case-eval k
;; Main keys to always include as-is
(clojure.core/into ()
(clojure.core/disj
taoensso.telemere.impl/standard-signal-keys
:msg_ :error :location :kvs :file :host :thread))
(assoc!* m k v)
;; Main keys to include with modified val
:error (if-let [chain (enc/ex-chain :as-map v)] (assoc! m k chain) m) ; As `expand-signal-error`
:msg_ (assoc!* m k (force v)) ; As `force-signal-msg`
;; Implementation keys to always exclude
(clojure.core/into ()
taoensso.telemere.impl/impl-signal-keys) m ; noop
;;; Other keys to exclude by default
:location (if incl-location? (assoc!* m k v) m)
:kvs (if incl-kvs-key? (assoc!* m k v) m)
:file (if incl-file? (assoc!* m k v) m)
:thread (if incl-thread? (assoc!* m k v) m)
:host (if incl-host? (assoc!* m k v) m)
;; Other (app-level) keys
(enc/cond
incl-kvs? (assoc!* m k v) ; Incl. all kvs
(contains? incl-keys k) (assoc!* m k v) ; Incl. specific kvs
:else m ; As `remove-signal-kvs`
)))
(transient {}) signal)))))))
(comment ((clean-signal-fn {:incl-keys #{:a}}) {:level :info, :id nil, :a "a", :b "b", :msg_ (delay "hi")}))
(defn pr-signal-fn
"Experimental, subject to change.
Returns a (fn pr [signal]) that:
@ -683,14 +752,6 @@
#?(:cljs :json ; Use js/JSON.stringify
:clj jsonista/write-value-as-string)})
Motivation:
Why use this util instead of just directly using the print function
given to `:pr-fn`? Signals are optimized for cheap creation and easy handling,
so may contain things like nil values and duplicated content.
This util efficiently clean signals of such noise, helping reduce
storage/transmission size, and making key info easier to see.
See also `format-signal-fn` for an alternative to `pr-signal-fn`
that produces human-readable output."
([] (pr-signal-fn nil))
@ -700,10 +761,10 @@
incl-newline? true}}]
(let [nl newline
clean-fn (clean-signal-fn opts)
pr-fn
(or
(case pr-fn
:none nil ; Undocumented, used for unit tests
:edn pr-edn
:json
#?(:cljs pr-json
@ -719,56 +780,13 @@
:param 'pr-fn
:expected
#?(:clj '#{:edn unary-fn}
:cljs '#{:edn :json unary-fn})}))))
assoc!*
(if-not incl-nils?
(fn [m k v] (if (nil? v) m (assoc! m k v))) ; As `remove-signal-nils`
(do assoc!))
incl-location? (contains? incl-keys :location)
incl-kvs-key? (contains? incl-keys :kvs)
incl-file? (contains? incl-keys :file)
incl-host? (contains? incl-keys :host)
incl-thread? (contains? incl-keys :thread)]
:cljs '#{:edn :json unary-fn})}))))]
(fn pr-signal [signal]
(when (map? signal)
(let [signal
(persistent!
(reduce-kv
(fn [m k v]
(enc/case-eval k
:msg_ (assoc!* m k (force v)) ; As force-signal-msg
:error (if-let [chain (enc/ex-chain :as-map v)] (assoc! m k chain) m) ; As expand-signal-error
;;; Keys excluded by default
:location (if incl-location? (assoc!* m k v) m)
:kvs (if incl-kvs-key? (assoc!* m k v) m)
:file (if incl-file? (assoc!* m k v) m)
:thread (if incl-thread? (assoc!* m k v) m)
:host (if incl-host? (assoc!* m k v) m)
(clojure.core/into ()
taoensso.telemere.impl/impl-signal-keys) m ; noop
(clojure.core/into ()
(clojure.core/disj
taoensso.telemere.impl/standard-signal-keys
:msg_ :error :location :kvs :file :host :thread))
(assoc!* m k v)
(if incl-kvs? (assoc!* m k v) m) ; As remove-signal-kvs
))
(transient {}) signal))]
(if-not pr-fn
signal
(let [output (pr-fn signal)]
(if incl-newline?
(str output nl)
(do output))))))))))
(if incl-newline?
(str (pr-fn (clean-fn signal)) nl)
(do (pr-fn (clean-fn signal)))))))))
(comment
(def s1 (tel/with-signal (tel/event! ::ev-id {:kvs {:k1 "v1"}})))

View file

@ -760,6 +760,28 @@
(is (= (utils/error-signal? {:level :fatal}) true))
(is (= (utils/error-signal? {:error? true}) true))])
(testing "clean-signal-fn"
(let [sig
{:level :info
:id nil
:msg_ (delay "msg")
:error ex2
:location "loc"
:kvs "kvs"
:file "file"
:thread "thread"
:a "a"
:b "b"}]
[(is (= ((utils/clean-signal-fn) sig) {:level :info, :msg_ "msg", :error ex2-chain}))
(is (= ((utils/clean-signal-fn {:incl-kvs? true}) sig) {:level :info, :msg_ "msg", :error ex2-chain, :a "a", :b "b"}))
(is (= ((utils/clean-signal-fn {:incl-nils? true}) sig) {:level :info, :msg_ "msg", :error ex2-chain, :id nil}))
(is (= ((utils/clean-signal-fn {:incl-keys #{:kvs}}) sig) {:level :info, :msg_ "msg", :error ex2-chain, :kvs "kvs"}))
(is (= ((utils/clean-signal-fn {:incl-keys #{:a}}) sig) {:level :info, :msg_ "msg", :error ex2-chain, :a "a"}))
(is (= ((utils/clean-signal-fn {:incl-keys
#{:location :kvs :file :thread}}) sig) {:level :info, :msg_ "msg", :error ex2-chain,
:location "loc", :kvs "kvs", :file "file", :thread "thread"}))]))
(testing "Misc utils"
[(is (= (utils/remove-signal-kvs {:a :A, :b :B, :kvs {:b :B}}) {:a :A}))
(is (= (utils/remove-signal-nils {:a :A, :b nil}) {:a :A}))
@ -836,27 +858,8 @@
"line" pnat-int?
"column" pnat-int?})))))
(testing "User pr-fn"
(is (= ((tel/pr-signal-fn {:pr-fn (fn [_] "str")}) sig) (str "str" utils/newline))))
(testing "Other options"
(let [sig
{:msg_ (delay "msg")
:error ex2
:id nil
:location "loc"
:kvs "kvs"
:file "file"
:thread "thread"
:user-key "user-val"}]
[(is (= ((tel/pr-signal-fn {:pr-fn :none}) sig) {:msg_ "msg", :error ex2-chain}))
(is (= ((tel/pr-signal-fn {:pr-fn :none, :incl-kvs? true}) sig) {:msg_ "msg", :error ex2-chain, :user-key "user-val"}))
(is (= ((tel/pr-signal-fn {:pr-fn :none, :incl-nils? true}) sig) {:msg_ "msg", :error ex2-chain, :id nil}))
(is (= ((tel/pr-signal-fn {:pr-fn :none, :incl-keys #{:kvs}}) sig) {:msg_ "msg", :error ex2-chain, :kvs "kvs"}))
(is (= ((tel/pr-signal-fn {:pr-fn :none, :incl-keys
#{:location :kvs :file :thread}}) sig) {:msg_ "msg", :error ex2-chain,
:location "loc", :kvs "kvs", :file "file", :thread "thread"}))]))]))
(testing "Custom pr-fn"
(is (= ((tel/pr-signal-fn {:pr-fn (fn [_] "str")}) sig) (str "str" utils/newline))))]))
(testing "format-signal-fn"
(let [sig (with-sig :raw :trap (tel/event! ::ev-id {:inst t1, :msg ["a" "b"]}))]