[new] Refactor impln of common signal creators

Objectives:

  - Support single map opts arg in all cases.

  - Make it easier for folks to inspect the source to understand how
    the common creators use/wrap underlying `signal!`.

Also updated relevant docstrings, etc.
This commit is contained in:
Peter Taoussanis 2024-12-21 11:11:47 +01:00
parent ace6e2dd2c
commit d2386d62f1
12 changed files with 265 additions and 376 deletions

View file

@ -1,10 +1,9 @@
Unconditionally executes given form and-
If form succeeds: return the form's result.
If form throws:
ALWAYS (unconditionally) executes given `run` form and:
If `run` form succeeds: return the form's result.
If `run` form throws:
Call `error!` with the thrown error and the given signal options [2],
then return (:catch-val opts) if it exists, or rethrow the error.
API: [form] [id-or-opts form] => form's result (value/throw) (unconditional), or (:catch-val opts)
Default kind: `:error`
Default level: `:error`

View file

@ -1,6 +1,7 @@
"Error" signal creator, emphasizing error + id.
"Error" signal creator, emphasizing (optional id) + error (Exception, etc.).
ALWAYS (unconditionally) returns the given error, so can conveniently be wrapped
by `throw`: (throw (error! (ex-info ...)), etc.
API: [error] [id-or-opts error] => given error (unconditional)
Default kind: `:error`
Default level: `:error`
@ -22,7 +23,6 @@ Tips:
- Supports the same options [2] as other signals [1].
- `error` arg is a platform error (`java.lang.Throwable` or `js/Error`).
- Can conveniently be wrapped by `throw`: (throw (error! ...)).
----------------------------------------------------------------------
[1] See `help:signal-creators` - (`signal!`, `log!`, `event!`, ...)

View file

@ -1,6 +1,6 @@
"Event" signal creator, emphasizing id + level.
"Event" signal creator, emphasizing id + (optional level).
Returns true iff signal was created (allowed by filtering).
API: [id] [id level-or-opts] => true iff signal was allowed
Default kind: `:event`
Default level: `:info`

View file

@ -1,6 +1,6 @@
"Log" signal creator, emphasizing message + level.
"Log" signal creator, emphasizing (optional level) + message.
Returns true iff signal was created (allowed by filtering).
API: [msg] [level-or-opts msg] => true iff signal was allowed.
Default kind: `:log`
Default level: `:info`

View file

@ -1,16 +1,17 @@
Low-level generic signal creator.
Low-level "generic" signal creator for creating signals of any "kind".
Takes a single map of options [2] with compile-time keys.
API: [opts] => depends on options [2]
Default kind: `:generic`
Return value depends on options:
- If given `:run` form: unconditionally returns run value, or rethrows run error.
- Otherwise: returns true iff signal was created (allowed by filtering).
Default kind: `:generic` (feel free to change!)
Default level: `:info`
When filtering conditions are met [4], creates a Telemere signal [3] and
dispatches it to registered handlers for processing (e.g. writing to
console/file/queue/db, etc.).
If `:run` option is provided: returns value of given run form, or throws.
Otherwise: returns true iff signal was created (allowed).
Generic signals are fairly low-level and useful mostly for library authors or
advanced users writing their own wrapper macros. Regular users will typically
prefer one of the higher-level signal creators optimized for ease-of-use in

View file

@ -14,13 +14,13 @@ various keys:
Creators vary only in in their default options and call APIs (expected args
and return values), making them more/less convenient for certain use cases:
`event!` -------- [id ] or [id opts/level] => true iff signal was created (allowed)
`log!` ---------- [msg ] or [opts/level msg] => true iff signal was created (allowed)
`error!` -------- [error] or [opts/id error] => given error (unconditional)
`trace!` -------- [form ] or [opts/id form] => form result (value/throw) (unconditional)
`spy!` ---------- [form ] or [opts/level form] => form result (value/throw) (unconditional)
`catch->error!` - [form ] or [opts/id form] => form value, or given fallback
`signal!` ------- [opts ] => depends on options
`signal!` ------- opts => allowed? / unconditional run result (value or throw)
`event!` -------- id + ?level => allowed?
`log!` ---------- ?level + msg => allowed?
`trace!` -------- ?id + run => unconditional run result (value or throw)
`spy!` ---------- ?level + run => unconditional run result (value or throw)
`error!` -------- ?id + error => unconditional given error
`catch->error!` - ?id + run => unconditional run value or ?catch-val
- `log!` and `event!` are both good default/general-purpose signal creators.
- `log!` emphasizes messages, while `event!` emphasizes ids.

View file

@ -1,6 +1,6 @@
"Spy" signal creator, emphasizing form + level.
"Spy" signal creator, emphasizing (optional level) + form to run.
ALWAYS (unconditionally) returns run value, or rethrows run error.
API: [form] [level-or-opts form] => form's result (value/throw) (unconditional)
Default kind: `:spy`
Default level: `:info`
@ -8,14 +8,14 @@ When filtering conditions are met [4], creates a Telemere signal [3] and
dispatches it to registered handlers for processing (e.g. writing to
console/file/queue/db, etc.).
Enables tracing of given `form` arg:
Enables tracing of given `run` form:
- Resulting signal will include {:keys [run-form run-val run-nsecs]}.
- Nested signals will include this signal's id and uid under `:parent`.
Limitations:
1. Traced code (`form` arg) is usually expected to be synchronous and eager.
1. Traced `run` form is usually expected to be synchronous and eager.
So no lazy seqs, async calls, or inversion of flow control (IoC) macros like
core.async `go` blocks, etc.
@ -47,18 +47,18 @@ Tips:
- Test using `with-signal`: (with-signal (spy! ...)).
- Supports the same options [2] as other signals [1].
- Identical to `trace!`, but emphasizes form + level rather than form + id.
- Like `trace!`, but takes optional level rather than optional id.
- Useful for debugging/monitoring forms, and tracing (nested) execution flow.
- Execution of `form` arg may create additional (nested) signals.
- Execution of `run` form may create additional (nested) signals.
Each signal's `:parent` key will indicate its immediate parent.
- Can be useful to wrap with `catch->error!`:
(catch->error! ::error-id (spy! ...)).
- Runtime of async or lazy code in `form` will intentionally NOT be included
in resulting signal's `:run-nsecs` value. If you want to measure such
runtimes, make sure that your form wraps where the relevant costs are
- Runtime of async or lazy code in `run` form will intentionally NOT be
included in resulting signal's `:run-nsecs` value. If you want to measure
such runtimes, make sure that your form wraps where the relevant costs are
actually realized. Compare:
(spy! (delay (my-slow-code))) ; Doesn't measure slow code
(spy! @(delay (my-slow-code))) ; Does measure slow code

View file

@ -1,6 +1,6 @@
"Trace" signal creator, emphasizing form + id.
"Trace" signal creator, emphasizing (optional id) + form to run.
ALWAYS (unconditionally) returns run value, or rethrows run error.
API: [form] [id-or-opts form] => form's result (value/throw) (unconditional)
Default kind: `:trace`
Default level: `:info` (intentionally NOT `:trace`!)
@ -8,14 +8,14 @@ When filtering conditions are met [4], creates a Telemere signal [3] and
dispatches it to registered handlers for processing (e.g. writing to
console/file/queue/db, etc.).
Enables tracing of given `form` arg:
Enables tracing of given `run` form:
- Resulting signal will include {:keys [run-form run-val run-nsecs]}.
- Nested signals will include this signal's id and uid under `:parent`.
Limitations:
1. Traced code (`form` arg) is usually expected to be synchronous and eager.
1. Traced `run` form is usually expected to be synchronous and eager.
So no lazy seqs, async calls, or inversion of flow control (IoC) macros like
core.async `go` blocks, etc.
@ -47,10 +47,10 @@ Tips:
- Test using `with-signal`: (with-signal (trace! ...)).
- Supports the same options [2] as other signals [1].
- Identical to `spy!`, but emphasizes form + id rather than form + level.
- Like `spy!`, but takes optional id rather than optional level.
- Useful for debugging/monitoring forms, and tracing (nested) execution flow.
- Execution of `form` arg may create additional (nested) signals.
- Execution of `run` form may create additional (nested) signals.
Each signal's `:parent` key will indicate its immediate parent.
- Can be useful to wrap with `catch->error!`:
@ -60,9 +60,9 @@ Tips:
refers to the general action of tracing program flow rather than to the
common logging level of the same name.
- Runtime of async or lazy code in `form` will intentionally NOT be included
in resulting signal's `:run-nsecs` value. If you want to measure such
runtimes, make sure that your form wraps where the relevant costs are
- Runtime of async or lazy code in `run` form will intentionally NOT be
included in resulting signal's `:run-nsecs` value. If you want to measure
such runtimes, make sure that your form wraps where the relevant costs are
actually realized. Compare:
(trace! (delay (my-slow-code))) ; Doesn't measure slow code
(trace! @(delay (my-slow-code))) ; Does measure slow code

View file

@ -198,154 +198,102 @@
(comment (enc/qb 1e6 (force *otel-tracer*))) ; 51.23
;;;; Signal creators
;; - event! [id ] [id opts/level] ; id + ?level => allowed? ; Sole signal with descending main arg!
;; - log! [msg ] [opts/level msg] ; msg + ?level => allowed?
;; - error! [error] [opts/id error] ; error + ?id => given error
;; - trace! [form ] [opts/id form] ; run + ?id => run result (value or throw)
;; - spy! [form ] [opts/level form] ; run + ?level => run result (value or throw)
;; - catch->error! [form ] [opts/id form] ; run + ?id => run value or ?return
;; - signal! [opts ] ; => allowed? / run result (value or throw)
;; - uncaught->error! [opts/id] ; ?id => nil
;; - signal! ---------- opts => allowed? / unconditional run result (value or throw)
;; - event! ----------- id + ?level => allowed?
;; - log! ------------- ?level + msg => allowed?
;; - trace! ----------- ?id + run => unconditional run result (value or throw)
;; - spy! ------------- ?level + run => unconditional run result (value or throw)
;; - error! ----------- ?id + error => unconditional given error
;; - catch->error! ---- ?id + run => unconditional run value or ?catch-val
;; - uncaught->error! - ?id => nil
#?(:clj
(defmacro event!
"[id] [id level-or-opts] => allowed?"
{:doc (impl/signal-docstring :event!)
:arglists (impl/signal-arglists :event!)}
[& args]
(let [opts
(impl/signal-opts `event! (enc/get-source &form &env)
{:kind :event, :level :info} :id :level :dsc args)]
`(impl/signal! ~opts))))
(defn- merge-or-assoc-opts [m &form &env k v]
(let [m (assoc m :location* (enc/get-source &form &env))]
(if (map? v)
(merge m v)
(assoc m k v)))))
#?(:clj
(let [base-opts {:kind :event, :level :info}]
(defmacro event!
"id + ?level => allowed? Note unique arg order: [x opts] rather than [opts x]!"
{:doc (impl/signal-docstring :event!)
:arglists (impl/signal-arglists :event!)}
([ opts-or-id] `(impl/signal! ~(merge-or-assoc-opts base-opts &form &env :id opts-or-id)))
([id opts-or-level] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form &env :level opts-or-level) :id id))))))
(comment (with-signal (event! ::my-id :info)))
#?(:clj
(defmacro log!
"[msg] [level-or-opts msg] => allowed?"
{:doc (impl/signal-docstring :log!)
:arglists (impl/signal-arglists :log!)}
[& args]
(let [opts
(impl/signal-opts `log! (enc/get-source &form &env)
{:kind :log, :level :info} :msg :level :asc args)]
`(impl/signal! ~opts))))
(let [base-opts {:kind :log, :level :info}]
(defmacro log!
"?level + msg => allowed?"
{:doc (impl/signal-docstring :log!)
:arglists (impl/signal-arglists :log!)}
([opts-or-msg ] `(impl/signal! ~(merge-or-assoc-opts base-opts &form &env :msg opts-or-msg)))
([opts-or-level msg] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form &env :level opts-or-level) :msg msg))))))
(comment (with-signal (log! :info "My msg")))
#?(:clj
(defmacro error!
"[error] [error id-or-opts] => error"
{:doc (impl/signal-docstring :error!)
:arglists (impl/signal-arglists :error!)}
[& args]
(let [opts
(impl/signal-opts `error! (enc/get-source &form &env)
{:kind :error, :level :error} :error :id :asc args)
error-form (get opts :error)]
(let [base-opts {:kind :trace, :level :info, :msg `impl/default-trace-msg}]
(defmacro trace!
"?id + run => unconditional run result (value or throw)."
{:doc (impl/signal-docstring :trace!)
:arglists (impl/signal-arglists :trace!)}
([opts-or-run] `(impl/signal! ~(merge-or-assoc-opts base-opts &form &env :run opts-or-run)))
([opts-or-id run] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form &env :id opts-or-id) :run run))))))
`(let [~'__error ~error-form]
(impl/signal! ~(assoc opts :error '__error))
~'__error ; Unconditional!
))))
(comment (with-signal (trace! ::my-id (+ 1 2))))
#?(:clj
(let [base-opts {:kind :spy, :level :info, :msg `impl/default-trace-msg}]
(defmacro spy!
"?level + run => unconditional run result (value or throw)."
{:doc (impl/signal-docstring :spy!)
:arglists (impl/signal-arglists :spy!)}
([opts-or-run] `(impl/signal! ~(merge-or-assoc-opts base-opts &form &env :run opts-or-run)))
([opts-or-level run] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form &env :level opts-or-level) :run run))))))
(comment (with-signals (spy! :info (+ 1 2))))
#?(:clj
(let [base-opts {:kind :error, :level :error}]
(defmacro error!
"?id + error => unconditional given error."
{:doc (impl/signal-docstring :error!)
:arglists (impl/signal-arglists :error!)}
([opts-or-id error] `(error! ~(assoc (merge-or-assoc-opts base-opts &form &env :id opts-or-id) :error error)))
([opts-or-error]
(let [opts (merge-or-assoc-opts base-opts &form &env :error opts-or-error)]
`(let [~'__error ~(get opts :error)]
(impl/signal! ~(assoc opts :error '__error))
(do ~'__error)))))))
(comment (with-signal (throw (error! ::my-id (ex-info "MyEx" {})))))
#?(:clj
(defmacro catch->error!
"[form] [id-or-opts form] => run value or ?catch-val"
{:doc (impl/signal-docstring :catch-to-error!)
:arglists (impl/signal-arglists :catch->error!)}
[& args]
(let [opts
(impl/signal-opts `catch->error! (enc/get-source &form &env)
{:kind :error, :level :error} ::__form :id :asc args)
(let [base-opts {:kind :error, :level :error}]
(defmacro catch->error!
"?id + run => unconditional run value or ?catch-val."
{:doc (impl/signal-docstring :catch->error!)
:arglists (impl/signal-arglists :catch->error!)}
([opts-or-id run] `(catch->error! ~(assoc (merge-or-assoc-opts base-opts &form &env :id opts-or-id) :run run)))
([opts-or-run]
(let [opts (merge-or-assoc-opts base-opts &form &env :run opts-or-run)
rethrow? (if (contains? opts :catch-val) false (get opts :rethrow? true))
catch-val (get opts :catch-val)
catch-sym (get opts :catch-sym '__caught-error) ; Undocumented
run-form (get opts :run)
opts (dissoc opts :run :catch-val :catch-sym :rethrow?)]
rethrow? (if (contains? opts :catch-val) false (get opts :rethrow? true))
catch-val (get opts :catch-val)
catch-sym (get opts :catch-sym '__caught-error) ; Undocumented
form (get opts ::__form)
opts (dissoc opts ::__form :catch-val :catch-sym :rethrow?)]
`(enc/try* ~run-form
(catch :all ~catch-sym
(impl/signal! ~(assoc opts :error catch-sym))
(if ~rethrow? (throw ~catch-sym) ~catch-val))))))))
`(enc/try* ~form
(catch :all ~catch-sym
(impl/signal! ~(assoc opts :error catch-sym))
(if ~rethrow? (throw ~catch-sym) ~catch-val))))))
(comment
(with-signal (catch->error! ::my-id (/ 1 0)))
(with-signal (catch->error! { :msg ["Error:" __caught-error]} (/ 1 0)))
(with-signal (catch->error! {:catch-sym my-err :msg ["Error:" my-err]} (/ 1 0))))
#?(:clj
(defmacro trace!
"[form] [id-or-opts form] => run result (value or throw)"
{:doc (impl/signal-docstring :trace!)
:arglists (impl/signal-arglists :trace!)}
[& args]
(let [opts
(impl/signal-opts `trace! (enc/get-source &form &env)
{:kind :trace, :level :info, :msg `impl/default-trace-msg}
:run :id :asc args)
;; :catch->error <id-or-opts> currently undocumented
[opts catch-opts] (impl/signal-catch-opts opts)]
(if catch-opts
`(catch->error! ~catch-opts (impl/signal! ~opts))
(do `(impl/signal! ~opts))))))
(comment
(with-signal (trace! ::my-id (+ 1 2)))
(let [[_ [s1 s2]]
(with-signals
(trace! {:id :id1, :catch->error :id2}
(throw (ex-info "Ex1" {}))))]
[s2]))
#?(:clj
(defmacro spy!
"[form] [level-or-opts form] => run result (value or throw)"
{:doc (impl/signal-docstring :spy!)
:arglists (impl/signal-arglists :spy!)}
[& args]
(let [opts
(impl/signal-opts `spy! (enc/get-source &form &env)
{:kind :spy, :level :info, :msg `impl/default-trace-msg}
:run :level :asc args)
;; :catch->error <id-or-opts> currently undocumented
[opts catch-opts] (impl/signal-catch-opts opts)]
(if catch-opts
`(catch->error! ~catch-opts (impl/signal! ~opts))
(do `(impl/signal! ~opts))))))
(comment (with-signal :force (spy! :info (+ 1 2))))
#?(:clj
(defmacro uncaught->error!
"Uses `uncaught->handler!` so that `error!` will be called for
uncaught JVM errors.
See `uncaught->handler!` and `error!` for details."
{:arglists (impl/signal-arglists :uncaught->error!)}
[& args]
(let [msg-form ["Uncaught Throwable on thread:" `(.getName ~(with-meta '__thread-arg {:tag 'java.lang.Thread}))]
opts
(impl/signal-opts `uncaught->error! (enc/get-source &form &env)
{:kind :error, :level :error, :msg msg-form}
:error :id :dsc (into ['__throwable-arg] args))]
`(uncaught->handler!
(fn [~'__thread-arg ~'__throwable-arg]
(impl/signal! ~opts))))))
(comment
(macroexpand '(uncaught->error! ::uncaught))
(do
(uncaught->error! ::uncaught)
(enc/threaded :user (/ 1 0))))
(comment (with-signal (catch->error! ::my-id (/ 1 0))))
#?(:clj
(defn uncaught->handler!
@ -361,6 +309,31 @@
(handler thread throwable)))))
nil))
#?(:clj
(let [base-opts
{:kind :error, :level :error,
:msg `["Uncaught Throwable on thread:" (.getName ~(with-meta '__thread-arg {:tag 'java.lang.Thread}))]
:error '__throwable-arg}]
(defmacro uncaught->error!
"Uses `uncaught->handler!` so that `error!` will be called for
uncaught JVM errors.
See `uncaught->handler!` and `error!` for details."
{:arglists (impl/signal-arglists :uncaught->error!)}
([ ] (enc/keep-callsite `(uncaught->error! {})))
([opts-or-id]
(let [opts (merge-or-assoc-opts base-opts &form &env :id opts-or-id)]
`(uncaught->handler!
(fn [~'__thread-arg ~'__throwable-arg]
(impl/signal! ~opts))))))))
(comment
(macroexpand '(uncaught->error! ::uncaught))
(do
(uncaught->error! ::uncaught)
(enc/threaded :user (/ 1 0))))
;;;;
(defn dispatch-signal!

View file

@ -376,84 +376,84 @@
(defn signal-arglists [macro-id]
(case macro-id
:signal! ; [opts] => allowed? / run result (value or throw)
'([{:as opts :keys
:signal! ; opts => allowed? / unconditional run result (value or throw)
'([{:as opts-map :keys
[#_defaults #_elide? #_allow? #_expansion-id, ; Undocumented
elidable? location #_location* inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
ctx ctx+ parent root trace?, do let data msg error run & kvs]}])
:signal-allowed?
'([{:as opts :keys
:signal-allowed? ; opts => allowed?
'([{:as opts-map :keys
[#_defaults #_elide? #_allow? #_expansion-id, ; Undocumented
elidable? location #_location* #_inst #_uid #_middleware #_middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
#_ctx #_ctx+ #_parent #_root #_trace?, #_do #_let #_data #_msg #_error #_run #_& #_kvs]}])
:event! ; [id] [id level-or-opts] => allowed?
'([id ]
[id level]
:event! ; id + ?level => allowed?
'([opts-or-id]
[id level]
[id
{:as opts :keys
{:as opts-map :keys
[#_defaults #_elide? #_allow? #_expansion-id,
elidable? location #_location* inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}])
:log! ; [msg] [level-or-opts msg] => allowed?
'([ msg]
[level msg]
[{:as opts :keys
:log! ; ?level + msg => allowed?
'([opts-or-msg]
[level msg]
[{:as opts-map :keys
[#_defaults #_elide? #_allow? #_expansion-id,
elidable? location #_location* inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
msg])
:error! ; [error] [id-or-opts error] => given error
'([ error]
[id error]
[{:as opts :keys
:trace! ; ?id + run => unconditional run result (value or throw)
'([opts-or-run]
[id run]
[{:as opts-map :keys
[#_defaults #_elide? #_allow? #_expansion-id,
elidable? location #_location* inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
ctx ctx+ parent root trace?, do let data msg error run & kvs]}
run])
:spy! ; ?level + run => unconditional run result (value or throw)
'([opts-or-run]
[level run]
[{:as opts-map :keys
[#_defaults #_elide? #_allow? #_expansion-id,
elidable? location #_location* inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
ctx ctx+ parent root trace?, do let data msg error run & kvs]}
run])
:error! ; ?id + error => unconditional given error
'([opts-or-error]
[id error]
[{:as opts-map :keys
[#_defaults #_elide? #_allow? #_expansion-id,
elidable? location #_location* inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
error])
:trace! ; [form] [id-or-opts form] => run result (value or throw)
'([ form]
[id form]
[{:as opts :keys
[#_defaults #_elide? #_allow? #_expansion-id,
elidable? location #_location* inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
ctx ctx+ parent root trace?, do let data msg error run & kvs]}
form])
:spy! ; [form] [level-or-opts form] => run result (value or throw)
'([ form]
[level form]
[{:as opts :keys
[#_defaults #_elide? #_allow? #_expansion-id,
elidable? location #_location* inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
ctx ctx+ parent root trace?, do let data msg error run & kvs]}
form])
:catch->error! ; [form] [id-or-opts form] => run result (value or throw)
'([ form]
[id form]
[{:as opts :keys
:catch->error! ; ?id + run => unconditional run value or ?catch-val
'([opts-or-run]
[id run]
[{:as opts-map :keys
[#_defaults #_elide? #_allow? #_expansion-id, rethrow? catch-val,
elidable? location #_location* inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
form])
run])
:uncaught->error! ; [] [id-or-opts] => nil
'([ ]
[id]
[{:as opts :keys
:uncaught->error! ; ?id => nil
'([]
[opts-or-id]
[{:as opts-map :keys
[#_defaults #_elide? #_allow? #_expansion-id,
elidable? location #_location* inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-limit-by,
@ -461,75 +461,6 @@
(enc/unexpected-arg! macro-id))))
#?(:clj
(defn signal-opts
"Util to help write common signal wrapper macros."
[context location* defaults main-key extra-key arg-order args]
(enc/cond
:let [context-name (str "`" (name context) "`")
num-args (count args)
bad-args!
(fn [msg data]
(throw
(ex-info (str "Invalid " context-name " args: " msg)
(conj
{:context context
:args args}
data))))]
(not (#{1 2} num-args))
(bad-args! (str "wrong number of args (" num-args ")")
{:actual num-args, :expected #{1 2}})
:let [[main-arg extra-arg]
(case arg-order
:dsc args ; [main ...]
:asc (reverse args) ; [... main]
(enc/unexpected-arg!
arg-order))
extra-arg? (= num-args 2)
extra-opts? (and extra-arg? (map? extra-arg))]
:do
(enc/cond
(and (map? main-arg) (not extra-arg?))
(bad-args! "single map arg is USUALLY a mistake, so isn't allowed. Please use 2 arg arity instead, or `signal!`." {})
(and extra-opts? (contains? extra-arg main-key))
(bad-args! (str "given opts should not contain `" main-key "`.") {}))
:let [base (merge defaults {:location* location*, main-key main-arg})]
extra-opts? (merge base extra-arg)
extra-arg? (merge base {extra-key extra-arg})
:else base)))
(comment (signal-opts `foo! :loc* {:level :info} :id :level :dsc [::my-id {:level :warn}]))
#?(:clj
(defn signal-catch-opts
"For use within `trace!` and `spy!`, etc."
[main-opts]
(let [catch-id-or-opts (get main-opts :catch->error)
main-opts (dissoc main-opts :catch->error)
catch-opts
(when catch-id-or-opts
(let [base ; Inherit some opts from main
(enc/assoc-some {}
:location* (get main-opts :location*)
:id (get main-opts :id))]
(cond
(true? catch-id-or-opts) (do base)
(map? catch-id-or-opts) (conj base catch-id-or-opts)
:else (conj base {:id catch-id-or-opts}))))]
[main-opts catch-opts])))
(comment
(signal-catch-opts {:id :main-id, :catch->error true})
(signal-catch-opts {:id :main-id, :catch->error :error-id})
(signal-catch-opts {:id :main-id, :catch->error {:id :error-id}}))
;;;; Signal macro
(deftype RunResult [value error ^long run-nsecs]

View file

@ -73,9 +73,9 @@
nil)))))
(comment
(macroexpand '(trace "foo"))
(tel/with-signal :force-msg (trace "foo"))
(tel/with-signal :force-msg (infof "Hello %s" "world")))
(macroexpand '(trace "foo"))
(tel/with-signal (trace "foo"))
(tel/with-signal (infof "Hello %s" "world")))
#?(:clj
(do
@ -103,24 +103,27 @@
([ form] (enc/keep-callsite `(spy! :debug nil ~form)))
([level form] (enc/keep-callsite `(spy! ~level nil ~form)))
([level form-name form]
(let [msg
(if-not form-name
`impl/default-trace-msg
`(fn [_form# value# error# nsecs#]
(impl/default-trace-msg ~form-name value# error# nsecs#)))]
(let [location* (enc/get-source &form &env)
msg
(if form-name
`(fn [_form# value# error# nsecs#] (impl/default-trace-msg ~form-name value# error# nsecs#))
`(fn [_form# value# error# nsecs#] (impl/default-trace-msg '~form value# error# nsecs#)))]
(enc/keep-callsite
`(tel/spy!
{:kind :spy
:level ~level
:id shim-id
:msg ~msg
:catch->error true}
`(tel/spy!
{:location* ~location*
:id shim-id
:level ~level
:msg ~msg}
(tel/catch->error!
{:location* ~location*
:id shim-id}
~form))))))
(comment
(select-keys (tel/with-signal :force-msg (spy! :info "my-form-name" (+ 1 2))) [:level :msg_])
(select-keys (tel/with-signal :force-msg (spy! :info "my-form-name" (throw (Exception. "Ex")))) [:level #_:msg_]))
(:level (tel/with-signal (spy! (/ 1 0))))
(select-keys (tel/with-signal (spy! :info #_"my-form-name" (+ 1 2))) [:level :msg_])
(select-keys (tel/with-signal (spy! :info #_"my-form-name" (throw (Exception. "Ex")))) [:level :msg_]))
#?(:clj (defmacro log-errors "Prefer `telemere/catch->error!`." [& body] (enc/keep-callsite `(tel/catch->error! {:id shim-id, :catch-val nil} (do ~@body)))))
#?(:clj (defmacro log-and-rethrow-errors "Prefer `telemere/catch->error!`." [& body] (enc/keep-callsite `(tel/catch->error! {:id shim-id} (do ~@body)))))

View file

@ -568,42 +568,61 @@
;;;;
(deftest _common-signals
[#?(:clj
(testing "signal-opts"
[(is (= (impl/signal-opts `foo! :loc* {:level :info} :id :level :dsc [::my-id ]) {:level :info, :id ::my-id, :location* :loc*}))
(is (= (impl/signal-opts `foo! :loc* {:level :info} :id :level :dsc [::my-id :warn ]) {:level :warn, :id ::my-id, :location* :loc*}))
(is (= (impl/signal-opts `foo! :loc* {:level :info} :id :level :dsc [::my-id {:level :warn}]) {:level :warn, :id ::my-id, :location* :loc*}))
[(testing "event!" ; id + ?level => allowed?
[(let [[[rv] [sv]] (with-sigs (tel/event! :id1 )) ] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :info, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/event! :id1 :warn)) ] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :warn, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/event! :id1 {:level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :warn, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/event! {:id :id1, :level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :warn, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/event! :id1 {:allow? false})) ] [(is (= rv nil)) (is (nil? sv))])])
(is (= (impl/signal-opts `foo! :loc* {:level :info} :id :level :asc [ ::my-id]) {:level :info, :id ::my-id, :location* :loc*}))
(is (= (impl/signal-opts `foo! :loc* {:level :info} :id :level :asc [:warn ::my-id]) {:level :warn, :id ::my-id, :location* :loc*}))
(is (= (impl/signal-opts `foo! :loc* {:level :info} :id :level :asc [{:level :warn} ::my-id]) {:level :warn, :id ::my-id, :location* :loc*}))
(testing "log!" ; ?level + msg => allowed?
[(let [[[rv] [sv]] (with-sigs (tel/log! "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :info}))])
(let [[[rv] [sv]] (with-sigs (tel/log! :warn "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :warn}))])
(let [[[rv] [sv]] (with-sigs (tel/log! {:level :warn} "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :warn}))])
(let [[[rv] [sv]] (with-sigs (tel/log! {:level :warn, :msg "msg"}))] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :warn}))])
(let [[[rv] [sv]] (with-sigs (tel/log! {:allow? false} "msg")) ] [(is (= rv nil)) (is (nil? sv))])])
(is (= (impl/signal-catch-opts {:id :main-id, :location* {:ns "ns"}, :catch->error true}) [{:id :main-id, :location* {:ns "ns"}} {:location* {:ns "ns"}, :id :main-id}]))
(is (= (impl/signal-catch-opts {:id :main-id, :location* {:ns "ns"}, :catch->error :error-id}) [{:id :main-id, :location* {:ns "ns"}} {:location* {:ns "ns"}, :id :error-id}]))
(is (= (impl/signal-catch-opts {:id :main-id, :location* {:ns "ns"}, :catch->error {:id :error-id}}) [{:id :main-id, :location* {:ns "ns"}} {:location* {:ns "ns"}, :id :error-id}]))
(testing "trace!" ; ?id + run => unconditional run result (value or throw)
[(let [[[rv] [sv]] (with-sigs (tel/trace! (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id nil, :msg_ "(+ 1 2) => 3"}))])
(let [[[rv] [sv]] (with-sigs (tel/trace! {:msg nil} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id nil, :msg_ nil}))])
(let [[[rv] [sv]] (with-sigs (tel/trace! :id1 (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/trace! {:id :id1} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/trace! {:id :id1, :run (+ 1 2)}))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id :id1}))])
(let [[[_ re] [sv]] (with-sigs (tel/trace! :id1 (ex1!))) ] [(is (ex1? re)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id :id1, :error pex1?,
:msg_ #?(:clj "(ex1!) !> clojure.lang.ExceptionInfo"
:cljs "(ex1!) !> cljs.core/ExceptionInfo")}))])
(let [[[rv] [sv]] (with-sigs (tel/trace! {:allow? false} (+ 1 2))) ] [(is (= rv 3)) (is (nil? sv))])
(is (throws? :ex-info "Invalid `foo!` args: single map arg is USUALLY a mistake" (impl/signal-opts `foo! :loc* {:level :info} :id :level :dsc [{:msg "msg"}])))
(is (throws? :ex-info "Invalid `foo!` args: given opts should not contain `:id`" (impl/signal-opts `foo! :loc* {:level :info} :id :level :dsc [:my-id1 {:id ::my-id2}])))]))
(testing ":run-form" ; Undocumented, experimental
[(is (sm? (with-sig (tel/trace! :non-list)) {:run-form :non-list}))
(is (sm? (with-sig (tel/trace! (+ 1 2 3 4))) {:run-form '(+ 1 2 3 4)}))
(is (sm? (with-sig (tel/trace! (+ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16))) {:run-form '(+ ...)}))
(is (sm? (with-sig (tel/trace! {:run-form my-run-form} (+ 1 2 3 4))) {:run-form 'my-run-form :kvs nil}))])
(testing "event!" ; id + ?level => allowed?
[(let [[[rv] [sv]] (with-sigs (tel/event! :id1 ))] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :info, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/event! :id1 :warn ))] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :warn, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/event! :id1 {:level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :warn, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/event! :id1 {:allow? false}))] [(is (= rv nil)) (is (nil? sv))])])
(testing ":run-val" ; Undocumented, experimental
[(is (sm? (with-sig (tel/trace! (+ 2 2))) {:run-val 4, :msg_ "(+ 2 2) => 4"}))
(is (sm? (with-sig (tel/trace! {:run-val "custom"} (+ 2 2))) {:run-val "custom", :msg_ "(+ 2 2) => custom", :kvs nil}))])])
(testing "error!" ; error + ?id => error
[(let [[[rv] [sv]] (with-sigs (tel/error! ex1))] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error pex1?, :id nil}))])
(let [[[rv] [sv]] (with-sigs (tel/error! :id1 ex1))] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error pex1?, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/error! {:id :id1} ex1))] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error pex1?, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/error! {:allow? false} ex1))] [(is (ex1? rv)) (is (nil? sv))])])
(testing "spy" ; ?level + run => unconditional run result (value or throw)
[(let [[[rv] [sv]] (with-sigs (tel/spy! (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :info, :msg_ "(+ 1 2) => 3"}))])
(let [[[rv] [sv]] (with-sigs (tel/spy! {:msg nil} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :info, :msg_ nil}))])
(let [[[rv] [sv]] (with-sigs (tel/spy! :warn (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn}))])
(let [[[rv] [sv]] (with-sigs (tel/spy! {:level :warn} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn}))])
(let [[[rv] [sv]] (with-sigs (tel/spy! {:level :warn, :run (+ 1 2)}))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn}))])
(let [[[_ re] [sv]] (with-sigs (tel/spy! :warn (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn, :error pex1?,
:msg_ #?(:clj "(ex1!) !> clojure.lang.ExceptionInfo"
:cljs "(ex1!) !> cljs.core/ExceptionInfo")}))])
(let [[[rv] [sv]] (with-sigs (tel/spy! {:allow? false} (+ 1 2))) ] [(is (= rv 3)) (is (nil? sv))])])
(testing "log!" ; msg + ?level => allowed?
[(let [[[rv] [sv]] (with-sigs (tel/log! "msg"))] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :info}))])
(let [[[rv] [sv]] (with-sigs (tel/log! :warn "msg"))] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :warn}))])
(let [[[rv] [sv]] (with-sigs (tel/log! {:level :warn} "msg"))] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :warn}))])
(let [[[rv] [sv]] (with-sigs (tel/log! {:allow? false} "msg"))] [(is (= rv nil)) (is (nil? sv))])])
(testing "error!" ; ?id + error => unconditional given error
[(let [[[rv] [sv]] (with-sigs (tel/error! ex1)) ] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error pex1?, :id nil}))])
(let [[[rv] [sv]] (with-sigs (tel/error! :id1 ex1)) ] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error pex1?, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/error! {:id :id1} ex1)) ] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error pex1?, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/error! {:id :id1, :error ex1}))] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error pex1?, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/error! {:allow? false} ex1)) ] [(is (ex1? rv)) (is (nil? sv))])
(let [c (enc/counter)] (tel/error! (do (c) ex1)) (is (= @c 1) "Error form evaluated exactly once"))])
(testing "catch->error!" ; form + ?id => run value or ?return
(testing "catch->error!" ; ?id + run => unconditional run value or ?return
[(let [[[rv re] [sv]] (with-sigs (tel/catch->error! (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
(let [[[rv re] [sv]] (with-sigs (tel/catch->error! (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error pex1?, :id nil}))])
(let [[[rv re] [sv]] (with-sigs (tel/catch->error! :id1 (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error pex1?, :id :id1}))])
@ -619,46 +638,6 @@
:data {:my-err my-err}} (ex1!)))]
[(is (= rv nil)) (is (sm? sv {:kind :error, :data {:my-err pex1?}}))])])
(testing "trace!" ; run + ?id => run result (value or throw)
[(let [[[rv] [sv]] (with-sigs (tel/trace! (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id nil, :msg_ "(+ 1 2) => 3"}))])
(let [[[rv] [sv]] (with-sigs (tel/trace! {:msg nil} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id nil, :msg_ nil}))])
(let [[[rv] [sv]] (with-sigs (tel/trace! :id1 (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id :id1}))])
(let [[[rv] [sv]] (with-sigs (tel/trace! {:id :id1} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id :id1}))])
(let [[[_ re] [sv]] (with-sigs (tel/trace! :id1 (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id :id1, :error pex1?,
:msg_ #?(:clj "(ex1!) !> clojure.lang.ExceptionInfo"
:cljs "(ex1!) !> cljs.core/ExceptionInfo")}))])
(let [[[rv] [sv]] (with-sigs (tel/trace! {:allow? false} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
(let [[_ [sv1 sv2]]
(with-sigs (tel/trace! {:id :id1, :catch->error :id2} (ex1!)))]
[(is (sm? sv1 {:kind :trace, :line :submap/some, :level :info, :id :id1}))
(is (sm? sv2 {:kind :error, :line :submap/some, :level :error, :id :id2}))
(is (= (:location sv1) (:location sv2)) "Error inherits exact same location")])
(testing ":run-form" ; Undocumented, experimental
[(is (sm? (with-sig (tel/trace! :non-list)) {:run-form :non-list}))
(is (sm? (with-sig (tel/trace! (+ 1 2 3 4))) {:run-form '(+ 1 2 3 4)}))
(is (sm? (with-sig (tel/trace! (+ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16))) {:run-form '(+ ...)}))
(is (sm? (with-sig (tel/trace! {:run-form my-run-form} (+ 1 2 3 4))) {:run-form 'my-run-form :kvs nil}))])
(testing ":run-val" ; Undocumented, experimental
[(is (sm? (with-sig (tel/trace! (+ 2 2))) {:run-val 4, :msg_ "(+ 2 2) => 4"}))
(is (sm? (with-sig (tel/trace! {:run-val "custom"} (+ 2 2))) {:run-val "custom", :msg_ "(+ 2 2) => custom", :kvs nil}))])])
(testing "spy" ; run + ?level => run result (value or throw)
[(let [[[rv] [sv]] (with-sigs (tel/spy! (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :info, :msg_ "(+ 1 2) => 3"}))])
(let [[[rv] [sv]] (with-sigs (tel/spy! {:msg nil} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :info, :msg_ nil}))])
(let [[[rv] [sv]] (with-sigs (tel/spy! :warn (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn}))])
(let [[[rv] [sv]] (with-sigs (tel/spy! {:level :warn} (+ 1 2)))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn}))])
(let [[[_ re] [sv]] (with-sigs (tel/spy! :warn (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn, :error pex1?,
:msg_ #?(:clj "(ex1!) !> clojure.lang.ExceptionInfo"
:cljs "(ex1!) !> cljs.core/ExceptionInfo")}))])
(let [[[rv] [sv]] (with-sigs (tel/spy! {:allow? false} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
(let [[_ [sv1 sv2]]
(with-sigs (tel/spy! {:id :id1, :catch->error :id2} (ex1!)))]
[(is (sm? sv1 {:kind :spy, :line :submap/some, :level :info, :id :id1}))
(is (sm? sv2 {:kind :error, :line :submap/some, :level :error, :id :id2}))
(is (= (:location sv1) (:location sv2)) "Error inherits exact same location")])])
#?(:clj
(testing "uncaught->error!"
(let [sv_ (atom ::nx)]
@ -769,9 +748,12 @@
(is (sm? (with-sig (timbre/info ex1 "x1" "x2")) {:kind :log, :level :info, :error pex1?, :msg_ "x1 x2", :data {:vargs ["x1" "x2"]}}) "First-arg error")
(is (sm? (with-sig (timbre/spy! :info "my-name" (+ 1 2))) {:kind :spy, :level :info, :id timbre/shim-id, :msg_ "my-name => 3", :ns pstr?}))
(is (sm? (with-sig (timbre/spy! (+ 1 2))) {:kind :spy, :level :debug, :id timbre/shim-id, :msg_ "(+ 1 2) => 3", :ns pstr?}))
(is (sm? (with-sig (timbre/spy! (ex1!))) {:kind :error, :level :error, :id timbre/shim-id, :msg_ nil, :error pex1?, :ns pstr?}))
(is (sm? (with-sig (timbre/spy! :info "my-name" (+ 1 2))) {:kind :spy, :level :info, :id timbre/shim-id, :msg_ "my-name => 3", :ns pstr?}))
(is (sm? (tel/with-min-level :debug (with-sig (timbre/spy! (+ 1 2)))) {:kind :spy, :level :debug, :id timbre/shim-id, :msg_ "(+ 1 2) => 3", :ns pstr?}))
(let [[_ [sv1 sv2]] (tel/with-min-level :debug (with-sigs (timbre/spy! (ex1!))))]
[(is (sm? sv1 {:kind :error, :level :error, :id timbre/shim-id, :msg_ nil, :error pex1?, :ns pstr?}))
(is (sm? sv2 {:kind :spy, :level :debug, :id timbre/shim-id, :msg_ pstr? :error pex1?, :ns pstr?}))])
(let [[[rv re] [sv]] (with-sigs (timbre/log-errors (ex1!)))] [(is (nil? re)) (is (sm? sv {:kind :error, :level :error, :error pex1?, :id timbre/shim-id}))])
(let [[[rv re] [sv]] (with-sigs (timbre/log-and-rethrow-errors (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :error, :level :error, :error pex1?, :id timbre/shim-id}))])])