[new] Add first handlers

This commit is contained in:
Peter Taoussanis 2024-03-21 12:36:55 +01:00
parent 126dc2aaaa
commit 1f14712950
5 changed files with 613 additions and 111 deletions

View file

@ -10,6 +10,7 @@
[taoensso.encore :as enc :refer [binding have have?]]
[taoensso.encore.signals :as sigs]
[taoensso.telemere.impl :as impl]
[taoensso.telemere.utils :as utils]
[taoensso.telemere.handlers :as handlers]
#?(:clj [taoensso.telemere.streams :as streams]))
@ -33,7 +34,10 @@
(enc/assert-min-encore-version [3 91 0])
;;;; TODO
;; - First handlers ns
;; - File handler (with rotating, rolling, etc.)
;; - Postal handler (or example)?
;; - Template / example handler?
;;
;; - Review, TODOs, missing docstrings
;; - Reading plan, wiki docs, explainer/demo video
;;
@ -341,18 +345,13 @@
(comment (check-interop))
;;;; Handler utils
(enc/defaliases
#?(:clj impl/thread-name)
#?(:clj impl/thread-id)
#?(:clj impl/hostname)
impl/format-instant
impl/format-error)
;;;; Handlers
;; TODO
(enc/defaliases handlers/console-handler-fn
#?(:cljs handlers/raw-console-handler-fn))
(add-handler! :default-console-handler
(console-handler-fn))
;;;; Flow benchmarks
@ -362,24 +361,43 @@
:clojure-version "1.11.1"
:java-version "OpenJDK 21"}
(binding [impl/*sig-handlers* nil]
[(enc/qb 1e6 ; [10.4 17.06 195.42 200.34]
[(binding [impl/*sig-handlers* nil]
(enc/qb 1e6 ; [10.4 17.06 195.42 200.34]
(signal! {:level :info, :run nil, :elide? true})
(signal! {:level :info, :run nil, :allow? false})
(signal! {:level :info, :run nil, :allow? true })
(signal! {:level :info, :run nil}))
(signal! {:level :info, :run nil})))
(binding [impl/*sig-handlers* nil]
(enc/qb 1e6 ; [8.1 15.35 647.82 279.67 682.1]
(signal! {:level :info, :run "run", :elide? true})
(signal! {:level :info, :run "run", :allow? false})
(signal! {:level :info, :run "run", :allow? true })
(signal! {:level :info, :run "run", :trace? false})
(signal! {:level :info, :run "run"}))
(signal! {:level :info, :run "run"})))
;; For README "performance" table
;; For README "performance" table
(binding [impl/*sig-handlers* nil]
(enc/qb [8 1e6] ; [9.23 197.2 277.55 649.32]
(signal! {:level :info, :elide? true})
(signal! {:level :info})
(signal! {:level :info, :run "run", :trace? false})
(signal! {:level :info, :run "run"}))]))
(signal! {:level :info, :run "run"})))])
;;;;
(comment
(with-handler :hid1 (handlers/console-handler-fn) {} (log! "Message"))
(let [sig
(with-signal
(event! ::ev-id
{:data {:a :A :b :b}
:error
(ex-info "Ex2" {:b :B}
(ex-info "Ex1" {:a :A}))}))]
(do ((handlers/console-handler-fn) sig))
#?(:cljs ((handlers/raw-console-handler-fn) sig))))
;;;;

View file

@ -1,10 +1,122 @@
(ns ^:no-doc taoensso.telemere.handlers
"Built-in Telemere handlers."
(:require
[clojure.string :as str]
[taoensso.encore :as enc :refer [have have?]]
[taoensso.telemere.impl :as impl]))
[clojure.string :as str]
[taoensso.encore :as enc :refer [have have?]]
[taoensso.telemere.utils :as utils]))
(comment
(require '[taoensso.telemere :as tel])
(remove-ns 'taoensso.telemere.handlers)
(:api (enc/interns-overview)))
#?(:clj
(defn console-handler-fn
"Experimental, subject to change.
Returns a (fn handler [signal]) that:
- Takes a Telemere signal.
- Writes a formatted signal string to stream.
Stream (`java.io.Writer`):
Defaults to `*err*` if `utils/error-signal?` is true, and `*out*` otherwise.
Common formatting alternatives:
(utils/format-signal-str->fn) ; For human-readable string output (default)
(utils/format-signal->edn-fn) ; For edn output
(utils/format-signal->json-fn) ; For JSON output
etc.
See each format builder for options, etc."
([] (console-handler-fn nil))
([{:keys [format-signal-fn stream]
:or {format-signal-fn (utils/format-signal->str-fn)}}]
(let [stream (case stream :*out* *out*, :*err* *err* stream)
error-signal? utils/error-signal?
nl utils/newline]
(fn console-handler [signal]
(let [^java.io.Writer stream
(or stream (if (error-signal? signal) *err* *out*))]
(when-let [output (format-signal-fn signal)]
(.write stream (str output nl))
(.flush stream)))))))
:cljs
(defn console-handler-fn
"Experimental, subject to change.
If `js/console` exists, returns a (fn handler [signal]) that:
- Takes a Telemere signal.
- Writes a formatted signal string to JavaScript console.
Common formatting alternatives:
(utils/format-signal-str->fn) ; For human-readable string output (default)
(utils/format-signal->edn-fn) ; For edn output
(utils/format-signal->json-fn) ; For JSON output
etc.
See each format builder for options, etc."
([] (console-handler-fn nil))
([{:keys [format-signal-fn]
:or {format-signal-fn (utils/format-signal->str-fn)}}]
(when (exists? js/console)
(let [js-console-logger utils/js-console-logger
nl utils/newline]
(fn console-handler [signal]
(when-let [output (format-signal-fn signal)]
(let [logger (js-console-logger (get signal :level))]
(.call logger logger (str output nl))))))))))
#?(:cljs
(defn- logger-fn [logger]
;; (fn [& xs] (.apply logger logger (into-array xs)))
(fn
([x1 ] (.call logger logger x1))
([x1 x2 ] (.call logger logger x1 x2))
([x1 x2 x3 ] (.call logger logger x1 x2 x3))
([x1 x2 x3 & more] (apply logger x1 x2 x3 more)))))
#?(:cljs
(defn raw-console-handler-fn
"Experimental, subject to change.
If `js/console` exists, returns a (fn handler [signal]) that:
- Takes a Telemere signal.
- Writes raw signal data to JavaScript console.
Intended for use with browser formatting tools like `binaryage/devtools`,
Ref. <https://github.com/binaryage/cljs-devtools>."
([] (raw-console-handler-fn nil))
([{:keys [format-signal-prelude-fn format-nsecs-fn] :as opts
:or
{format-signal-prelude-fn (utils/format-signal-prelude-fn) ; (fn [signal])
format-nsecs-fn (utils/format-nsecs-fn) ; (fn [nanosecs])
}}]
(when (and (exists? js/console) (exists? js/console.group))
(let [js-console-logger utils/js-console-logger
handle-signal-content-fn ; (fn [signal hf vf]
(utils/handle-signal-content-fn
{:format-nsecs-fn format-nsecs-fn
:format-error-fn nil
:raw-error? true})]
(fn raw-console-handler [signal]
(let [{:keys [level error]} signal
logger (js-console-logger level)]
;; Unfortunately groups have no level
(.group js/console (format-signal-prelude-fn signal))
(handle-signal-content-fn signal (logger-fn logger) identity)
(when-let [stack (and error (.-stack (enc/ex-root error)))]
(.call logger logger stack))
(.groupEnd js/console))))))))

View file

@ -5,9 +5,7 @@
(:refer-clojure :exclude [binding])
(:require
[taoensso.encore :as enc :refer [binding have have?]]
[taoensso.encore.signals :as sigs]
#?(:clj [clj-commons.format.exceptions :as fmt-ex])
#?(:clj [clj-commons.ansi :as fmt-ansi])))
[taoensso.encore.signals :as sigs]))
(comment
(remove-ns 'taoensso.telemere.impl)
@ -664,82 +662,3 @@
{:handle? false}))]
(= (force (get signal :msg_)) msg)))
;;;; Handler utils
#?(:clj (defn thread-name ^String [] (.getName (Thread/currentThread))))
#?(:clj (defn thread-id ^String [] (.getId (Thread/currentThread))))
(comment (thread-name) (thread-id))
#?(:clj
(defn hostname
"Returns local cached hostname string, or `timeout-val` (default \"UnknownHost\")."
(^String [ ] (enc/get-hostname (enc/msecs :mins 1) 5000 "UnknownHost"))
( [timeout-msecs timeout-val] (enc/get-hostname (enc/msecs :mins 1) timeout-msecs timeout-val))))
(comment (enc/qb 1e6 (hostname))) ; 69.13
(defn format-instant
"TODO Docstring"
{:tag #?(:clj 'String :cljs 'string)}
([instant] (format-instant nil instant))
#?(:cljs
([{:keys [format]} instant]
(if format ; `goog.i18n.DateTimeFormat`
(.format format instant)
(.toISOString instant)))
:clj
([{:keys [formatter]
:or {formatter java.time.format.DateTimeFormatter/ISO_INSTANT}}
instant]
(.format
^java.time.format.DateTimeFormatter formatter
^java.time.Instant instant))))
(comment (format-instant (enc/now-inst)))
(defn format-error
"TODO Docstring"
{:tag #?(:clj 'String :cljs 'string)}
([error] (format-error nil error))
#?(:cljs
([_ error]
(let [nl newline]
(str
(or
(.-stack error) ; Incl. `ex-message` content
(ex-message error))
(when-let [data (ex-data error)] (str nl "ex-data:" nl " " (pr-str data)))
(when-let [cause (ex-cause error)] (str nl nl "Caused by:" nl (format-error cause))))))
:clj
;; TODO Review API, esp. re: *color-enabled*, etc.
([{:keys [use-fonts? sort-stacktrace-by fonts]
:or
{use-fonts? :auto
sort-stacktrace-by :chronological #_:depth-first
fonts clj-commons.format.exceptions/default-fonts}}
error]
(binding [fmt-ansi/*color-enabled*
(if (enc/identical-kw? use-fonts? :auto)
nil ; => Guess based on environment
use-fonts?)
fmt-ex/*fonts* fonts
fmt-ex/*traditional*
(case sort-stacktrace-by
:depth-first true ; Traditional
:chronological false ; Modern (default)
(enc/unexpected-arg! sort-stacktrace-by
{:context `format-error
:param 'sort-stacktrace-by
:expected #{:depth-first :chronological}}))]
(fmt-ex/format-exception error)))))
(comment (println (format-error (ex-info "Ex2" {:k2 :v2} (ex-info "Ex1" {:k1 :v1})))))

View file

@ -0,0 +1,365 @@
(ns ^:no-doc taoensso.telemere.utils
"Misc utils useful for Telemere handlers, middleware, etc."
(:refer-clojure :exclude [newline])
(:require
[clojure.string :as str]
[taoensso.encore :as enc :refer [have have?]]))
(comment
(require '[taoensso.telemere :as tel])
(remove-ns 'taoensso.telemere.utils)
(:api (enc/interns-overview)))
;;;; Private
(enc/def* ^:no-doc upper-qn
"Private, don't use.
`:foo/bar` -> \"FOO/BAR\", etc."
{:tag #?(:clj 'String :cljs 'string)}
(enc/fmemoize (fn [x] (str/upper-case (enc/as-qname x)))))
(comment (upper-qn :foo/bar))
(enc/def* ^:no-doc format-level
"Private, don't use.
`:info` -> \"INFO\",
`5` -> \"LEVEL:5\", etc."
{:tag #?(:clj 'String :cljs 'string)}
(enc/fmemoize
(fn [x]
(if (keyword? x)
(upper-qn x)
(str "LEVEL:" x)))))
(comment (format-level :info))
(enc/def* ^:no-doc format-id
"Private, don't use.
`:foo.bar/baz` -> \"::baz\", etc."
{:tag #?(:clj 'String :cljs 'string)}
(enc/fmemoize
(fn [ns x]
(if (keyword? x)
(if (= (namespace x) ns)
(str "::" (name x))
(str x))
(str x)))))
(comment (format-id (str *ns*) ::id1))
;;;; Public misc
(enc/defaliases enc/newline enc/pr-edn enc/pr-json)
#?(:clj (defn thread-name "Returns string name of current thread." ^String [] (.getName (Thread/currentThread))))
#?(:clj (defn thread-id "Returns long id of current thread." ^long [] (.getId (Thread/currentThread))))
(comment [(thread-name) (thread-id)])
#?(:clj
(defn host-ip
"Returns cached local host IP address string, or `timeout-val` (default \"UnknownHost\")."
( [timeout-msecs timeout-val] (enc/get-host-ip (enc/msecs :mins 1) timeout-msecs timeout-val))
(^String [ ] (enc/get-host-ip (enc/msecs :mins 1) 5000 "UnknownHost"))))
#?(:clj
(defn hostname
"Returns cached local hostname string, or `timeout-val` (default \"UnknownHost\")."
( [timeout-msecs timeout-val] (enc/get-hostname (enc/msecs :mins 1) timeout-msecs timeout-val))
(^String [ ] (enc/get-hostname (enc/msecs :mins 1) 3500 (delay (host-ip 1500 "UnknownHost"))))))
(comment (enc/qb 1e6 (hostname))) ; 56.88
#?(:cljs
(defn js-console-logger
"Returns JavaScript console logger to match given signal level:
`:trace` -> `js/console.trace`,
`:error` -> `js/console.error`, etc.
Defaults to `js.console.log` for unmatched signal levels.
NB: assumes that `js/console` exists, handler builders should check first!"
[level]
(case level
:trace js/console.trace
:debug js/console.debug
:info js/console.info
:warn js/console.warn
:error js/console.error
:fatal js/console.error
:report js/console.info
(do js/console.log))))
(comment (js-console-logger))
(defn error-signal?
"Experimental, subject to change.
Returns true iff given signal has an `:error` value, or a `:kind` or `:level`
that indicates that it's an error."
#?(:cljs {:tag 'string})
[signal]
(and signal
(boolean
(or
(get signal :error)
(enc/identical-kw? (get signal :kind) :error)
(case (get signal :level) (:error :fatal) true false)
(get signal :error?) ; User kv
))))
(comment (error-signal? {:level :fatal}))
(defn error-in-signal->maps
"Experimental, subject to change.
Returns given signal with possible `:error` replaced by
[{:keys [type msg data]} ...] cause chain.
Useful when serializing signals to edn/JSON/etc."
[signal]
(enc/if-let [error (get signal :error)
chain (enc/ex-chain :as-map error)]
(assoc signal :error chain)
(do signal)))
(comment (error-in-signal->maps {:level :info :error (ex-info "Ex" {})}))
(defn minify-signal
"Experimental, subject to change.
Returns minimal signal map, removing:
- Keys with nil values, and
- Keys with redundant values (`:extra-kvs`, `:location`, `:file`).
Useful when serializing signals to edn/JSON/etc."
[signal]
(reduce-kv
(fn [m k v]
(if (nil? v)
m
(case k
(:extra-kvs :location :file) m
(assoc m k v))))
nil signal))
(comment
(minify-signal (tel/with-signal (tel/event! ::ev-id1)))
(let [s (tel/with-signal (tel/event! ::ev-id1))]
(enc/qb 1e6 ; 683
(minify-signal s))))
;;;; Formatters
(defn format-nsecs-fn
"Experimental, subject to change.
Returns a (fn format [nanosecs]) that:
- Takes a long nanoseconds (e.g. runtime).
- Returns a formatted human-readable string like:
\"1.00m\", \"4.20s\", \"340ms\", \"822μs\", etc."
([] (format-nsecs-fn nil))
([{:as _opts}] (fn format-nsecs [nanosecs] (enc/format-nsecs nanosecs))))
(comment ((format-nsecs-fn) 4747463567))
(enc/defalias enc/format-inst-fn)
(comment ((format-inst-fn) (enc/now-inst)))
(defn format-error-fn
"Experimental, subject to change.
Returns a (fn format [error]) that:
- Takes a platform error (`Throwable` or `js/Error`).
- Returns a formatted human-readable string"
([] (format-error-fn nil))
([{:as _opts}]
(let [nl enc/newline
nls enc/newlines]
(fn format-error [error]
(when-let [em (enc/ex-map error)]
(let [sb (enc/str-builder)
s+ (partial enc/sb-append sb)
{:keys [chain trace]} em]
(let [s++ (enc/sb-appender sb (str nls "Caused: "))]
(s+ " Root: ")
(doseq [{:keys [type msg data]} (rseq chain)]
(s++ type " - " msg)
(when data
(s+ nl " data: " (enc/pr-edn* data)))))
(when trace
(s+ nl nl "Root stack trace:")
#?(:cljs (s+ nl trace)
:clj
(doseq [st-el (force trace)]
(let [{:keys [class method file line]} st-el]
(s+ nl "" class "/" method " at " file ":" line)))))
(str sb)))))))
(comment
(do (throw (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"}))))
(do (enc/ex-map (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"}))))
(println (str "--\n" ((format-error-fn) (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"}))))))
(defn format-signal-prelude-fn
"Experimental, subject to change.
Returns a (fn format [signal]) that:
- Takes a Telemere signal.
- Returns a formatted prelude string like:
\"2024-03-26T11:14:51.806Z INFO EVENT Hostname taoensso.telemere(2,21) ::ev-id - msg\""
([] (format-signal-prelude-fn nil))
([{:keys [format-inst-fn]
:or {format-inst-fn (format-inst-fn)}}]
(fn format-signal-prelude [signal]
(let [{:keys [inst level kind ns id msg_]} signal
sb (enc/str-builder)
s+ (enc/sb-appender sb " ")]
(when inst (when-let [ff format-inst-fn] (s+ (ff inst))))
(when level (s+ (format-level level)))
(if kind (s+ (upper-qn kind)) (s+ "DEFAULT"))
#?(:clj (s+ (hostname)))
;; "<ns>:(<line>,<column>)"
(when-let [base (or ns (get signal :file))]
(let [s+ (partial enc/sb-append sb)] ; Without separator
(s+ " " base)
(when-let [l (get signal :line)]
(s+ "(" l)
(when-let [c (get signal :column)] (s+ "," c))
(s+ ")"))))
(when id (s+ (format-id ns id)))
(when-let [msg (force msg_)] (s+ "- " msg))
(str sb)))))
(comment ((format-signal-prelude-fn) (tel/with-signal (tel/event! ::ev-id))))
(defn ^:no-doc handle-signal-content-fn
"Private, don't use.
Returns a (fn handle [signal handle-fn value-fn]) for internal use.
Content equivalent to `format-signal-prelude-fn`."
([] (handle-signal-content-fn nil))
([{:keys [format-nsecs-fn format-error-fn raw-error?]
:or
{format-nsecs-fn (format-nsecs-fn) ; (fn [nanosecs])
format-error-fn (format-error-fn) ; (fn [error])
}}]
(let [err-start (str newline "<<< error <<<" newline)
err-stop (str newline ">>> error >>>")]
(fn handle-signal-content [signal hf vf]
(let [{:keys [uid parent data extra-kvs ctx sample-rate]} signal]
(when sample-rate (hf "sample: " (vf sample-rate)))
(when uid (hf " uid: " (vf uid)))
(when parent (hf "parent: " (vf parent)))
(when data (hf " data: " (vf data)))
(when extra-kvs (hf " kvs: " (vf extra-kvs)))
(when ctx (hf " ctx: " (vf ctx))))
(let [{:keys [run-form error]} signal]
(when run-form
(let [{:keys [run-val run-nsecs]} signal
run-time (when run-nsecs (when-let [ff format-nsecs-fn] (ff run-nsecs)))
run-info
(if error
{:form run-form
:time run-time
:nsecs run-nsecs}
{:form run-form
:time run-time
:nsecs run-nsecs
:val run-val
#?@(:clj [:val-type (enc/class-sym run-val)])})]
(hf " run: " (vf run-info))))
(when error
(if raw-error?
(hf " error: " error)
(when-let [ff format-error-fn]
(hf err-start (ff error) err-stop)))))))))
;;;; Signal formatters
(defn format-signal->edn-fn
"Experimental, subject to change.
Returns a (fn format->edn [signal]) that:
- Takes a Telemere signal.
- Returns edn string of the (minified) signal."
([] (format-signal->edn-fn nil))
([{:keys [pr-edn-fn prep-fn]
:or
{pr-edn-fn pr-edn
prep-fn (comp error-in-signal->maps minify-signal)}}]
(fn format-signal->edn [signal]
(let [signal* (if prep-fn (prep-fn signal) signal)]
(pr-edn-fn signal*)))))
(comment ((format-signal->edn-fn) {:level :info, :msg "msg"}))
(defn format-signal->json-fn
"Experimental, subject to change.
Returns a (fn format->json [signal]) that:
- Takes a Telemere signal.
- Returns JSON string of the (minified) signal."
([] (format-signal->json-fn nil))
([{:keys [pr-json-fn prep-fn]
:or
{pr-json-fn pr-json
prep-fn (comp error-in-signal->maps minify-signal)}}]
(enc/try*
(pr-json-fn "telemere/auto-test")
(catch :all t
(throw (ex-info (str "`" `format-signal->json-fn "` `:pr-json` test failed") {} t))))
(fn format-signal->json [signal]
(let [signal* (if prep-fn (prep-fn signal) signal)]
(pr-json-fn signal*)))))
(comment ((format-signal->json-fn) {:level :info, :msg "msg"}))
(defn format-signal->str-fn
"Experimental, subject to change.
Returns a (fn format->str [signal]) that:
- Takes a Telemere signal.
- Returns a formatted string intended for text consoles, etc."
([] (format-signal->str-fn nil))
([{:keys [format-signal-prelude-fn
format-nsecs-fn format-error-fn]
:or
{format-signal-prelude-fn (format-signal-prelude-fn) ; (fn [signal])
format-nsecs-fn (format-nsecs-fn) ; (fn [nanosecs])
format-error-fn (format-error-fn) ; (fn [error])
}}]
(let [handle-signal-content-fn ; (fn [signal hf vf]
(handle-signal-content-fn
{:format-nsecs-fn format-nsecs-fn
:format-error-fn format-error-fn})]
(fn format-signal->str [signal]
(let [sb (enc/str-builder)
s+ (partial enc/sb-append sb)
s++ (partial enc/sb-append sb (str newline " "))]
(when-let [ff format-signal-prelude-fn] (s+ (ff signal))) ; Prelude
(handle-signal-content-fn signal s++ enc/pr-edn) ; Content
(str sb))))))
(comment
(tel/with-ctx {:c :C}
(println
((format-signal->str-fn)
(tel/with-signal
(tel/event! ::ev-id
{:user-k1 #{:a :b :c}
:msg "hi"
:data {:a :A}
;; :error (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"}))
:run (/ 1 0)}))))))

View file

@ -5,8 +5,10 @@
[taoensso.encore.signals :as sigs]
[taoensso.telemere :as tel]
[taoensso.telemere.impl :as impl]
[taoensso.telemere.utils :as utils]
#?(:clj [taoensso.telemere.slf4j :as slf4j])
#?(:clj [clojure.tools.logging :as ctl]))
#?(:clj [clojure.tools.logging :as ctl])
#?(:clj [jsonista.core :as jsonista]))
#?(:cljs
(:require-macros
@ -29,13 +31,29 @@
(defmacro wst [form] `(impl/-with-signals (fn [] ~form) {:trap-errors? true}))
(defmacro ws1 [form] `(let [[_# [s1#]] (impl/-with-signals (fn [] ~form) {:force-msg? true})] s1#))))
(do
(def ex1 (ex-info "TestEx" {}))
(def ex1-pred (enc/pred #(= % ex1)))
(defn ex1! [] (throw ex1)))
(def ^:dynamic *dynamic-var* nil)
(def ex1 (ex-info "Ex1" {}))
(def ex2 (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"})))
(def ex1-pred (enc/pred #(= % ex1)))
(def ex2-type (#'enc/ex-type ex2))
(defn ex1! [] (throw ex1))
(def t0s "2024-06-09T21:15:20.170Z")
(def t0 (enc/as-inst t0s))
(def udt0 (enc/as-udt t0))
;; (tel/remove-handler! :default-console-handler)
(let [sig-handlers_ (atom nil)]
(test/use-fixtures :once
(enc/test-fixtures
{:after (fn [] (enc/set-var-root! impl/*sig-handlers* @sig-handlers_))
:before
(fn []
(reset! sig-handlers_ impl/*sig-handlers*)
(enc/set-var-root! impl/*sig-handlers* nil))})))
;;;;
#?(:clj
@ -61,8 +79,8 @@
(is (= (ws (sig! {:level :info, :allow? false })) [nil nil]) "With runtime suppression")
(is (= (ws (sig! {:level :info, :allow? false, :run (+ 1 2)})) [3 nil]) "With runtime suppression, run-form")
(is (->> (sig! {:level :info, :elide? true, :run (ex1!)}) (throws? :ex-info "TestEx")) "With compile-time elision, throwing run-form")
(is (->> (sig! {:level :info, :allow? false, :run (ex1!)}) (throws? :ex-info "TestEx")) "With runtime suppression, throwing run-form")
(is (->> (sig! {:level :info, :elide? true, :run (ex1!)}) (throws? :ex-info "Ex1")) "With compile-time elision, throwing run-form")
(is (->> (sig! {:level :info, :allow? false, :run (ex1!)}) (throws? :ex-info "Ex1")) "With runtime suppression, throwing run-form")
(let [[rv1 [sv1]] (ws (sig! {:level :info }))
[rv2 [sv2]] (ws (sig! {:level :info, :run (+ 1 2)}))]
@ -548,6 +566,76 @@
[(is (sm? (ws1 (-> sl (.info "Hello"))) {:level :info, :ctx {"k1" "v1", "k2" "v2"}}) "Legacy API: MDC")
(is (sm? (ws1 (-> (.atInfo sl) (.log "Hello"))) {:level :info, :ctx {"k1" "v1", "k2" "v2"}}) "Fluent API: MDC")])))])])]))
;;;; Utils
(deftest _utils
[(testing "Basic utils"
[(is (= (utils/upper-qn :foo/bar) "FOO/BAR"))
(is (= (utils/format-level :info) "INFO"))
(is (= (utils/format-level 8) "LEVEL:8"))
(is (= (utils/format-id "foo.bar" :foo.bar/qux) "::qux"))
(is (= (utils/format-id "foo.baz" :foo.bar/qux) ":foo.bar/qux"))])
(testing "error-signal?"
[(is (= (utils/error-signal? {:error nil}) false))
(is (= (utils/error-signal? {:error ex1}) true))
(is (= (utils/error-signal? {:kind :error}) true))
(is (= (utils/error-signal? {:level :error}) true))
(is (= (utils/error-signal? {:level :fatal}) true))
(is (= (utils/error-signal? {:error? true}) true))])
(testing "Formatters, etc."
[(is (= (utils/error-in-signal->maps {:level :info, :error ex2})
{:level :info, :error [{:type ex2-type, :msg "Ex2", :data {:k2 "v2"}}
{:type ex2-type, :msg "Ex1", :data {:k1 "v1"}}]}))
(is (= (utils/minify-signal {:level :info, :location {:ns "ns"}, :file "file"}) {:level :info}))
(is (= ((utils/format-nsecs-fn) 1.5e9) "1.50s")) ; More tests in Encore
(is (= ((utils/format-inst-fn) t0) "2024-06-09T21:15:20.170Z"))
(is (enc/str-starts-with? ((utils/format-error-fn) ex2)
#?(:clj " Root: clojure.lang.ExceptionInfo - Ex1\n data: {:k1 \"v1\"}\n\nCaused: clojure.lang.ExceptionInfo - Ex2\n data: {:k2 \"v2\"}\n\nRoot stack trace:\n"
:cljs " Root: cljs.core/ExceptionInfo - Ex1\n data: {:k1 \"v1\"}\n\nCaused: cljs.core/ExceptionInfo - Ex2\n data: {:k2 \"v2\"}\n\nRoot stack trace:\n")))
(let [sig (tel/with-signal (tel/event! ::ev-id {:inst t0}))
prelude ((utils/format-signal-prelude-fn) sig)] ; "2024-06-09T21:15:20.170Z INFO EVENT taoensso.telemere-tests(592,35) ::ev-id"
[(is (enc/str-starts-with? prelude "2024-06-09T21:15:20.170Z INFO EVENT"))
(is (enc/str-ends-with? prelude "::ev-id"))
(is (string? (re-find #"taoensso.telemere-tests\(\d+,\d+\)" prelude)))])
(testing "format-signal->edn-fn"
(let [sig (update (tel/with-signal (tel/event! ::ev-id {:inst t0})) :inst enc/inst->udt)
sig* (enc/read-edn ((utils/format-signal->edn-fn) sig))]
(is
(enc/submap? sig*
{:schema 1, :kind :event, :id ::ev-id, :level :info,
:ns "taoensso.telemere-tests"
:inst udt0
:line (enc/pred enc/int?)
:column (enc/pred enc/int?)}))))
(testing "format-signal->json-fn"
(let [sig (tel/with-signal (tel/event! ::ev-id {:inst t0}))
sig* (enc/read-json ((utils/format-signal->json-fn) sig))]
(is
(enc/submap? sig*
{"schema" 1, "kind" "event", "id" "taoensso.telemere-tests/ev-id",
"level" "info", "ns" "taoensso.telemere-tests",
"inst" t0s
"line" (enc/pred enc/int?)
"column" (enc/pred enc/int?)}))))
(testing "format-signal->str-fn"
(let [sig (tel/with-signal (tel/event! ::ev-id {:inst t0}))]
(is (enc/str-starts-with? ((utils/format-signal->str-fn) sig)
"2024-06-09T21:15:20.170Z INFO EVENT"))))])])
;;;; Handlers
;; TODO
;;;;
#?(:cljs