From 1f1471295011932da4b1330fb60209310913d53e Mon Sep 17 00:00:00 2001 From: Peter Taoussanis Date: Thu, 21 Mar 2024 12:36:55 +0100 Subject: [PATCH] [new] Add first handlers --- src/taoensso/telemere.cljc | 54 ++-- src/taoensso/telemere/handlers.cljc | 118 ++++++++- src/taoensso/telemere/impl.cljc | 83 +------ src/taoensso/telemere/utils.cljc | 365 ++++++++++++++++++++++++++++ test/taoensso/telemere_tests.cljc | 104 +++++++- 5 files changed, 613 insertions(+), 111 deletions(-) create mode 100644 src/taoensso/telemere/utils.cljc diff --git a/src/taoensso/telemere.cljc b/src/taoensso/telemere.cljc index dc8d644..6b691b4 100644 --- a/src/taoensso/telemere.cljc +++ b/src/taoensso/telemere.cljc @@ -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)))) + +;;;; diff --git a/src/taoensso/telemere/handlers.cljc b/src/taoensso/telemere/handlers.cljc index 950f025..3384be7 100644 --- a/src/taoensso/telemere/handlers.cljc +++ b/src/taoensso/telemere/handlers.cljc @@ -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. ." + + ([] (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)))))))) diff --git a/src/taoensso/telemere/impl.cljc b/src/taoensso/telemere/impl.cljc index aec11d5..15acdb6 100644 --- a/src/taoensso/telemere/impl.cljc +++ b/src/taoensso/telemere/impl.cljc @@ -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}))))) diff --git a/src/taoensso/telemere/utils.cljc b/src/taoensso/telemere/utils.cljc new file mode 100644 index 0000000..59b39fe --- /dev/null +++ b/src/taoensso/telemere/utils.cljc @@ -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))) + + ;; ":(,)" + (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)})))))) diff --git a/test/taoensso/telemere_tests.cljc b/test/taoensso/telemere_tests.cljc index 245320b..c9b7541 100644 --- a/test/taoensso/telemere_tests.cljc +++ b/test/taoensso/telemere_tests.cljc @@ -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