mirror of
https://github.com/taoensso/telemere.git
synced 2025-12-22 11:41:11 +00:00
[new] Add first handlers
This commit is contained in:
parent
126dc2aaaa
commit
1f14712950
5 changed files with 613 additions and 111 deletions
|
|
@ -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))))
|
||||
|
||||
;;;;
|
||||
|
|
|
|||
|
|
@ -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))))))))
|
||||
|
|
|
|||
|
|
@ -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})))))
|
||||
|
|
|
|||
365
src/taoensso/telemere/utils.cljc
Normal file
365
src/taoensso/telemere/utils.cljc
Normal 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)}))))))
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue