diff --git a/project.clj b/project.clj index 7a18e46..b436211 100644 --- a/project.clj +++ b/project.clj @@ -8,7 +8,7 @@ :url "https://www.eclipse.org/legal/epl-v10.html"} :dependencies - [[com.taoensso/encore "3.105.1"]] + [[com.taoensso/encore "3.107.0"]] :test-paths ["test" #_"src"] @@ -16,7 +16,7 @@ {;; :default [:base :system :user :provided :dev] :provided {:dependencies [[org.clojure/clojurescript "1.11.132"] [org.clojure/clojure "1.11.3"]]} - :c1.12 {:dependencies [[org.clojure/clojure "1.12.0-alpha10"]]} + :c1.12 {:dependencies [[org.clojure/clojure "1.12.0-alpha11"]]} :c1.11 {:dependencies [[org.clojure/clojure "1.11.3"]]} :c1.10 {:dependencies [[org.clojure/clojure "1.10.1"]]} @@ -48,10 +48,12 @@ [com.taoensso/slf4j-telemere "1.0.0-beta5"] #_[org.slf4j/slf4j-simple "2.0.13"] #_[org.slf4j/slf4j-nop "2.0.13"] - [com.draines/postal "2.0.5"] + + ;;; For optional handlers [io.opentelemetry/opentelemetry-api "1.37.0"] #_[io.opentelemetry/opentelemetry-sdk-extension-autoconfigure "1.37.0"] - #_[io.opentelemetry/opentelemetry-exporter-otlp "1.37.0"]] + #_[io.opentelemetry/opentelemetry-exporter-otlp "1.37.0"] + [com.draines/postal "2.0.5"]] :plugins [[lein-pprint "1.3.2"] diff --git a/resources/signal-docstrings/signal-formatters.txt b/resources/signal-docstrings/signal-formatters.txt index 66f0226..605f804 100644 --- a/resources/signal-docstrings/signal-formatters.txt +++ b/resources/signal-docstrings/signal-formatters.txt @@ -1,5 +1,5 @@ Common signal formatters include: - (utils/format-signal-str->fn) {}) ; For human-readable string output (default) + (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 diff --git a/shadow-cljs.edn b/shadow-cljs.edn index b647912..6c2ee88 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -1,7 +1,7 @@ {;;:lein true :source-paths ["src" "test"] :dependencies - [[com.taoensso/encore "3.105.1"] + [[com.taoensso/encore "3.107.0"] [cider/cider-nrepl "0.47.0"] [binaryage/devtools "1.0.7"]] diff --git a/src/taoensso/telemere.cljc b/src/taoensso/telemere.cljc index 9148db1..ce5e10b 100644 --- a/src/taoensso/telemere.cljc +++ b/src/taoensso/telemere.cljc @@ -32,9 +32,10 @@ (remove-ns 'taoensso.telemere) (:api (enc/interns-overview))) -(enc/assert-min-encore-version [3 105 1]) +(enc/assert-min-encore-version [3 107 0]) ;;;; TODO +;; - Add handlers: Logstash, Slack, Carmine, Datadog, Kafka ;; - Native OpenTelemetry traces and spans ;; - Update Tufte (signal API, config API, signal keys, etc.) ;; - Update Timbre (signal API, config API, signal keys, backport improvements) @@ -118,12 +119,12 @@ #?(:clj (defmacro set-ctx! "Set `*ctx*` var's root (base) value. See `*ctx*` for details." - [root-val] `(enc/set-var-root! *ctx* ~root-val))) + [root-ctx-val] `(enc/set-var-root! *ctx* ~root-ctx-val))) #?(:clj (defmacro with-ctx "Evaluates given form with given `*ctx*` value. See `*ctx*` for details." - [init-val form] `(binding [*ctx* ~init-val] ~form))) + [ctx-val form] `(binding [*ctx* ~ctx-val] ~form))) (comment (with-ctx "my-ctx" *ctx*)) diff --git a/src/taoensso/telemere/consoles.cljc b/src/taoensso/telemere/consoles.cljc index 86983a3..8b1d6ad 100644 --- a/src/taoensso/telemere/consoles.cljc +++ b/src/taoensso/telemere/consoles.cljc @@ -18,7 +18,7 @@ Returns a (fn handler [signal]) that: - Takes a Telemere signal. - - Writes a formatted signal string to stream. + - Writes formatted signal string to stream. A general-purpose `println`-style handler that's well suited for outputting signals formatted as edn, JSON, or human-readable strings. @@ -34,8 +34,7 @@ :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] + error-signal? utils/error-signal?] (fn a-handler:console ([]) ; Shut down (no-op) @@ -52,7 +51,7 @@ If `js/console` exists, returns a (fn handler [signal]) that: - Takes a Telemere signal. - - Writes a formatted signal string to JavaScript console. + - Writes formatted signal string to JavaScript console. A general-purpose `println`-style handler that's well suited for outputting signals formatted as edn, JSON, or human-readable strings. @@ -65,8 +64,7 @@ :or {format-signal-fn (utils/format-signal->str-fn)}}] (when (exists? js/console) - (let [js-console-logger utils/js-console-logger - nl utils/newline] + (let [js-console-logger utils/js-console-logger] (fn a-handler:console ([]) ; Shut down (no-op) diff --git a/src/taoensso/telemere/files.clj b/src/taoensso/telemere/files.clj index 50f9b10..29e1089 100644 --- a/src/taoensso/telemere/files.clj +++ b/src/taoensso/telemere/files.clj @@ -270,7 +270,7 @@ Returns a (fn handler [signal]) that: - Takes a Telemere signal. - - Writes a formatted signal string to file. + - Writes formatted signal string to file. Signals will be appended to file specified by `path`. Depending on options, archives may be maintained: diff --git a/src/taoensso/telemere/impl.cljc b/src/taoensso/telemere/impl.cljc index e2d0b1d..d9ef676 100644 --- a/src/taoensso/telemere/impl.cljc +++ b/src/taoensso/telemere/impl.cljc @@ -201,14 +201,6 @@ (defmacro with-tracing "Wraps `form` with tracing iff const boolean `trace?` is true." [trace? id uid form] - - ;; Not much motivation to support runtime `trace?` form, but easy - ;; to add support later if desired - (when-not (enc/const-form? trace?) - (enc/unexpected-arg! trace? - {:msg "Expected constant (compile-time) `:trace?` value" - :context `with-tracing})) - (if trace? `(binding [*trace-parent* (TraceParent. ~id ~uid)] ~form) (do form)))) @@ -580,7 +572,14 @@ kind-form :kind id-form :id} opts - trace? (get opts :trace? (boolean run-form)) + trace? (get opts :trace? (boolean run-form)) + _ + (when-not (contains? #{true false nil} trace?) + ;; Not much motivation to support runtime `trace?` form, but easy + ;; to add support later if desired + (enc/unexpected-arg! trace? + {:msg "Expected constant (compile-time) `:trace?` boolean" + :context `with-tracing})) inst-form (get opts :inst :auto) inst-form (if (= inst-form :auto) `(enc/now-inst*) inst-form) @@ -588,7 +587,7 @@ uid-form (get opts :uid (when trace? :auto/uuid)) uid-form (parse-uid-form uid-form) - signal-form + signal-delay-form (let [{do-form :do let-form :let msg-form :msg @@ -599,8 +598,9 @@ let-form (or let-form '[]) msg-form (parse-msg-form msg-form) - ctx-form (get opts :ctx `taoensso.telemere/*ctx*) - parent-form (get opts :parent (when trace? `taoensso.telemere.impl/*trace-parent*)) + ctx-form (get opts :ctx `taoensso.telemere/*ctx*) + parent-form (get opts :parent (when trace? `taoensso.telemere.impl/*trace-parent*)) + middleware-form (get opts :middleware `taoensso.telemere/*middleware*) kvs-form (not-empty @@ -610,6 +610,7 @@ :ctx :parent #_:trace?, :do :let :data :msg :error :run :elide? :allow? #_:expansion-id))] + ;; Compile-time validation (when (and run-form error-form) (throw ; Prevent ambiguity re: source of error (ex-info "Signals cannot have both `:run` and `:error` opts at the same time" @@ -618,23 +619,29 @@ :location location :other-opts (dissoc opts :run :error)}))) - ;; Eval let bindings AFTER call filtering but BEFORE data, msg - `(do + `(delay + ;; Delay (cache) shared by all handlers. Covers signal `:let` eval, signal construction, + ;; middleware (possibly expensive), etc. Throws here will be caught by handler. ~do-form - (let ~let-form ; Allow to throw during `signal-value_` deref - (new-signal ~'__inst ~'__uid - ~location ~'__ns ~line-form ~column-form ~file-form, - ~sample-rate-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form, - ~kvs-form ~data-form ~msg-form, - '~run-form ~'__run-result ~error-form)))) + (let [~@let-form ; Allow to throw, eval BEFORE data, msg, etc. + ~'__signal + (new-signal ~'__inst ~'__uid + ~location ~'__ns ~line-form ~column-form ~file-form, + ~sample-rate-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form, + ~kvs-form ~data-form ~msg-form, + '~run-form ~'__run-result ~error-form)] - run-fn-form (when run-form `(fn [] (~run-form)))] + ;; Final unwrapped signal value visible to users/handler-fns, allow to throw + (if-let [call-middleware# ~middleware-form] + ((sigs/get-middleware-fn call-middleware#) ~'__signal) ; Can throw + (do ~'__signal)))))] ;; Could avoid double `run-form` expansion with a fn wrap (>0 cost) - ;; `(let [~'run-fn-form ~run-fn-form] - ;; (if-not ~allow? - ;; (run-fn-form) - ;; (let [...]))) + ;; (let [run-fn-form (when run-form `(fn [] (~run-form)))] + ;; `(let [~'run-fn-form ~run-fn-form] + ;; (if-not ~allow? + ;; (run-fn-form) + ;; (let [...])))) `(enc/if-not ~allow? ; Allow to throw at call ~run-form @@ -645,33 +652,23 @@ ~'__uid ~uid-form ; '' ~'__ns ~ns-form ; '' - ~'__call-middleware ~(get opts :middleware `taoensso.telemere/*middleware*) ~'__run-result ; Non-throwing (traps) ~(when run-form - `(let [~'__t0 (enc/now-nano*)] + `(let [t0# (enc/now-nano*)] (with-tracing ~trace? ~'__id ~'__uid (enc/try* - (do (RunResult. ~run-form nil (- (enc/now-nano*) ~'__t0))) - (catch :all ~'__t (RunResult. nil ~'__t (- (enc/now-nano*) ~'__t0))))))) + (do (RunResult. ~run-form nil (- (enc/now-nano*) t0#))) + (catch :all t# (RunResult. nil t# (- (enc/now-nano*) t0#))))))) - ~'__signal_ - (delay - ;; Cache shared by all handlers. Covers signal `:let` eval, signal construction, - ;; middleware (possibly expensive), etc. + signal_# ~signal-delay-form] - ;; The unwrapped signal value actually visible to users/handler-fns, realized only - ;; AFTER handler filtering. Allowed to throw on deref (handler will catch). - (let [~'__signal ~signal-form] ; Can throw - (if ~'__call-middleware - ((sigs/get-middleware-fn ~'__call-middleware) ~'__signal) ; Can throw - (do ~'__signal))))] - - ;; Unconditionally send same wrapped signal to all handlers. - ;; Each handler will then use wrapper for filtering, unwrapping allowed signals. - (dispatch-signal! (WrappedSignal. ~'__ns ~'__kind ~'__id ~'__level ~'__signal_)) + (dispatch-signal! ; Runner preserves dynamic bindings when async. + ;; Unconditionally send same wrapped signal to all handlers. Each handler will + ;; use wrapper for handler filtering, unwrapping (realizing) only allowed signals. + (WrappedSignal. ~'__ns ~'__kind ~'__id ~'__level signal_#)) (if ~'__run-result - (do (~'__run-result ~'__signal_)) + (do (~'__run-result signal_#)) true)))))))) (comment diff --git a/src/taoensso/telemere/postal.clj b/src/taoensso/telemere/postal.clj index 3aababc..e283788 100644 --- a/src/taoensso/telemere/postal.clj +++ b/src/taoensso/telemere/postal.clj @@ -55,7 +55,7 @@ Returns a (fn handler [signal]) that: - Takes a Telemere signal. - - Sends an email with formatted signal content to the configured recipient. + - Sends formatted signal string as email to specified recipient. Useful for emailing important alerts to admins, etc. diff --git a/src/taoensso/telemere/utils.cljc b/src/taoensso/telemere/utils.cljc index f18fec7..17ed62d 100644 --- a/src/taoensso/telemere/utils.cljc +++ b/src/taoensso/telemere/utils.cljc @@ -249,7 +249,7 @@ :writer/state {:file file, :stream (.deref stream_)} (when (open?_) (let [content content-or-action - ba (.getBytes (str content) java.nio.charset.StandardCharsets/UTF_8)] + ba (enc/str->utf8-ba (str content))] (locking lock (try (file-exists!) diff --git a/test/taoensso/telemere_tests.cljc b/test/taoensso/telemere_tests.cljc index 9140eee..1fd8a55 100644 --- a/test/taoensso/telemere_tests.cljc +++ b/test/taoensso/telemere_tests.cljc @@ -240,6 +240,10 @@ (is (= rv4 true)) (is (= sv4 "signal-value")) (is (= @c 7) "3x run + 4x middleware")])) + (testing "Binding conveyance" + (binding [*dynamic-var* :foo] + (is (sm? (with-sig (sig! {:level :info, :data {:dynamic-var *dynamic-var*}})) {:data {:dynamic-var :foo}})))) + #?(:clj (testing "Printing" (let [sv1 (with-sig (sig! {:level :info, :run (+ 1 2), :my-k1 :my-v1}))