diff --git a/projects/main/src/taoensso/telemere.cljc b/projects/main/src/taoensso/telemere.cljc index 0dd1861..23fa23d 100644 --- a/projects/main/src/taoensso/telemere.cljc +++ b/projects/main/src/taoensso/telemere.cljc @@ -68,6 +68,7 @@ #?(:clj impl/with-signal) #?(:clj impl/with-signals) #?(:clj impl/signal!) + #?(:clj impl/signal-allowed?) ;; Utils utils/format-signal-fn diff --git a/projects/main/src/taoensso/telemere/impl.cljc b/projects/main/src/taoensso/telemere/impl.cljc index b3faaef..21fb85a 100644 --- a/projects/main/src/taoensso/telemere/impl.cljc +++ b/projects/main/src/taoensso/telemere/impl.cljc @@ -381,6 +381,13 @@ sample-rate kind ns id level when rate-limit, ctx parent root trace?, do let data msg error run & kvs]}]) + :signal-allowed? + '([{:as opts :keys + [#_defaults #_elide? #_allow? #_expansion-id, ; Undocumented + elidable? location #_location* #_inst #_uid #_middleware, + sample-rate kind ns id level when rate-limit, + #_ctx #_parent #_root #_trace?, #_do #_let #_data #_msg #_error #_run #_& #_kvs]}]) + :event! ; [id] [id level-or-opts] => allowed? '([id ] [id level] @@ -762,11 +769,27 @@ (signal! {:level :info, :run "run"})))) #?(:clj - (defmacro signal-allowed? - "Used only for interop (tools.logging, SLF4J, etc.)." - {:arglists (signal-arglists :signal!)} + (defmacro ^:public signal-allowed? + "Returns true iff signal with given opts would meet filtering conditions: + (when (signal-allowed? {:level :warn, <...>}) (my-custom-code)) + + Allows you to use Telemere's rich filtering system for conditionally + executing arbitrary code. Also handy for batching multiple signals + under a single set of conditions (incl. rate-limiting, sampling, etc.): + + ;; Logs exactly 2 or 0 messages (never 1): + (when (signal-allowed? {:level :info, :sample-rate 0.5}) + (log! {:allow? true} \"Message 1\") + (log! {:allow? true} \"Message 2\"))" + + ;; Used also for interop (tools.logging, SLF4J), etc. + {:arglists (signal-arglists :signal-allowed?)} [opts] - (let [{:keys [#_expansion-id #_location elide? allow?]} + (have? map? opts) + (let [defaults (get opts :defaults) + opts (merge defaults (dissoc opts :defaults)) + + {:keys [#_expansion-id #_location elide? allow?]} (sigs/filterable-expansion {:sf-arity 4 :ct-sig-filter ct-sig-filter @@ -774,7 +797,9 @@ (assoc opts :location* (get opts :location* (enc/get-source &form &env))))] - (and (not elide?) allow?)))) + (if elide? false `(if ~allow? true false))))) + +(comment (macroexpand '(signal-allowed? {:level :info}))) ;;;; Interop diff --git a/projects/main/test/taoensso/telemere_tests.cljc b/projects/main/test/taoensso/telemere_tests.cljc index 312eec3..a3ae1a9 100644 --- a/projects/main/test/taoensso/telemere_tests.cljc +++ b/projects/main/test/taoensso/telemere_tests.cljc @@ -275,7 +275,10 @@ [(is (= sv1 (read-string (pr-str sv1))))]))) - (is (sm? (with-sig (shell/signal! {:level :info})) {:level :info, :ns "taoensso.telemere-tests", :line :submap/some}) "Shell API")]) + (testing "Shell API" + [(is (sm? (with-sig (shell/signal! {:level :info})) {:level :info, :ns "taoensso.telemere-tests", :line :submap/some})) + (is (true? (tel/with-min-level :debug (shell/signal-allowed? {:level :debug})))) + (is (false? (tel/with-min-level :debug (shell/signal-allowed? {:level :trace}))))])]) (deftest _handlers ;; Basic handler tests are in Encore diff --git a/projects/shell/src/taoensso/telemere/shell.cljc b/projects/shell/src/taoensso/telemere/shell.cljc index 27241f8..f105d0f 100644 --- a/projects/shell/src/taoensso/telemere/shell.cljc +++ b/projects/shell/src/taoensso/telemere/shell.cljc @@ -12,13 +12,58 @@ (remove-ns 'taoensso.telemere.shell) (:api (enc/interns-overview))) +;;;; Private + #?(:clj (defmacro ^:private compile-if [test then else] (if (try (eval test) (catch Throwable _ false)) then else))) -(def ^:private telemere-present? - "Is Telemere present (not necessarily loaded)?" - (compile-if (jio/resource "taoensso/telemere.cljc") true false)) +#?(:clj + (def ^:private telemere-present? + "Is Telemere present (not necessarily loaded)?" + (compile-if (jio/resource "taoensso/telemere.cljc") true false))) + +#?(:clj + (defn- require-telemere! [] + (try + (require 'taoensso.telemere) ; For macro expansion + (catch Exception e + (throw + (ex-info "Failed to require `taoensso.telemere` - `(require-telemere-if-present)` call missing?" + {} e)))))) + +#?(:clj + (defn- get-source "From Encore" [macro-form macro-env] + (let [{:keys [line column file]} (meta macro-form) + file + (if-not (:ns macro-env) + *file* ; Compiling Clj + (or ; Compiling Cljs + (when-let [url (and file (try (jio/resource file) (catch Exception _)))] + (try (.getPath (jio/file url)) (catch Exception _)) + (do (str url))) + file)) + + file + (when (string? file) + (when-not (contains? #{"NO_SOURCE_PATH" "NO_SOURCE_FILE" ""} file) + file)) + + m {:ns (str *ns*)} + m (if line (assoc m :line line) m) + m (if column (assoc m :column column) m) + m (if file (assoc m :file file) m)] + m))) + +#?(:clj + (defn- signal-opts [macro-form macro-env opts] + (if (map? opts) + (conj {:location* (get-source macro-form macro-env)} (dissoc opts :fallback)) + (throw + (ex-info "Signal opts must be a map" + {:given {:value opts, :type (type opts)}}))))) + +;;;; Public #?(:clj (defmacro if-telemere @@ -92,28 +137,12 @@ ctx parent root trace?, do let data msg error run & kvs]}])} [opts] - (if (map? opts) - (if telemere-present? - (do - (try - (require 'taoensso.telemere) ; For macro expansion - (catch Exception e - (throw - (ex-info "Failed to require `taoensso.telemere` - `(require-telemere-if-present)` call missing?" - {} e)))) - - (with-meta ; Keep callsite - `(taoensso.telemere/signal! ~(dissoc opts :fallback)) - (meta &form))) - - (let [fb-form (get opts :fallback)] - (if-let [run-form (get opts :run)] - `(let [run-result# ~run-form] ~fb-form run-result#) - (do fb-form)))) - - (throw - (ex-info "`signal!` expects map opts" - {:given {:value opts, :type (type opts)}}))))) + (if telemere-present? + (do (require-telemere!) `(taoensso.telemere/signal! ~(signal-opts &form &env opts))) + (let [fb-form (get opts :fallback)] + (if-let [run-form (get opts :run)] + `(let [run-result# ~run-form] ~fb-form run-result#) + (do fb-form)))))) (comment (macroexpand @@ -123,3 +152,32 @@ :msg ["Hello" "world" x] :data {:a :A :x x} :fallback (println (str "Hello world " x))}))) + +#?(:clj + (defmacro signal-allowed? + "Experimental, subject to change. + Returns true iff Telemere is present and signal with given opts would meet + filtering conditions. + + MUST be used with `require-telemere-if-present`, example: + + (ns my-lib (:require [taoensso.telemere.shell :as t])) + (t/require-telemere-if-present) ; Just below `ns` form! + + (when (t/signal-allowed? {:level :warn, <...>}) + (my-custom-code))" + + {:arglists + '([{:as opts :keys + [#_fallback, ; Unique to shell + #_defaults #_elide? #_allow? #_expansion-id, ; Undocumented + elidable? location #_location* #_inst #_uid #_middleware, + sample-rate kind ns id level when rate-limit, + #_ctx #_parent #_root #_trace?, #_do #_let #_data #_msg #_error #_run #_& #_kvs]}])} + + [opts] + (if telemere-present? + (do (require-telemere!) `(taoensso.telemere/signal-allowed? ~(signal-opts &form &env opts))) + (get opts :fallback nil)))) + +(comment (macroexpand '(signal-allowed? {:level :warn, :sample-rate 0.5})))