[mod] [#53] Breaking: change return value of log!, event!

This change will only affect rare advanced users that depend on
the return value of `log!` or `event!`. For all other users this
will be a non-breaking change.

Before this commit:
  `log!` and `event!` returned true iff signal was allowed.

After this commit:
  `log!`  and `event!` now ALWAYS return nil.
  `log!?` and `event!?` have been added that keep the old behaviour.

Motivation:
  It's pretty rare to use the return value when generating log or event
  signals. I originally included the return value since it CAN be handy,
  and I figured it could just be ignored by those that don't need it.

  But #53 showed that there's a downside I hadn't anticipated - some
  users may actually depend on / prefer a nil return to prevent
  accidentally affecting program flow.

  I think that's a legitimate enough concern to still make a change now
  before v1 final.

  Apologies for the nuissance!
This commit is contained in:
Peter Taoussanis 2025-03-03 09:36:40 +01:00
parent e32ed8deb5
commit ac5feb4723
13 changed files with 93 additions and 69 deletions

View file

@ -235,13 +235,13 @@ See relevant docstrings (links below) for usage info-
| Name | Kind | Args | Returns | | Name | Kind | Args | Returns |
| :---------------------------------------------------------------------------------------------------------- | :--------- | :--------------- | :--------------------------- | | :---------------------------------------------------------------------------------------------------------- | :--------- | :--------------- | :--------------------------- |
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts | | [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `:log` | `?level` + `msg` | nil |
| [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `:event` | `id` + `?level` | Signal allowed? | | [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `:event` | `id` + `?level` | nil |
| [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `:log` | `?level` + `msg` | Signal allowed? |
| [`trace!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#trace!) | `:trace` | `?id` + `run` | Form result | | [`trace!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#trace!) | `:trace` | `?id` + `run` | Form result |
| [`spy!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#spy!) | `:spy` | `?level` + `run` | Form result | | [`spy!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#spy!) | `:spy` | `?level` + `run` | Form result |
| [`error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#error!) | `:error` | `?id` + `error` | Given error | | [`error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#error!) | `:error` | `?id` + `error` | Given error |
| [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) | `:error` | `?id` | Form value or given fallback | | [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) | `:error` | `?id` | Form value or given fallback |
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts |
### Internal help ### Internal help

View file

@ -1,11 +1,12 @@
ALWAYS (unconditionally) executes given `run` form and: ALWAYS (unconditionally) executes given `run` form and:
If `run` form succeeds: returns the form's result.
If `run` form throws ANYTHING:
Calls `error!` with the thrown error and given signal options [2], then
either returns given (:catch-val opts), or rethrows.
Default kind: `:error` Default kind: `:error`
Default level: `:error` Default level: `:error`
Returns:
- If given `run` form succeeds: returns the form's result.
- If given `run` form throws ANYTHING:
Calls `error!` with the thrown error and given signal options [2], then
either returns given (:catch-val opts), or rethrows.
Just a convenience util. For more flexibility use your own `try/catch`. Just a convenience util. For more flexibility use your own `try/catch`.
See `taoensso.encore/try*` for easily catching cross-platform errors. See `taoensso.encore/try*` for easily catching cross-platform errors.

View file

@ -1,9 +1,10 @@
"Error" signal creator, emphasizing (optional id) + error (Exception, etc.). "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.
Default kind: `:error` Default kind: `:error`
Default level: `:error` Default level: `:error`
Returns:
ALWAYS (unconditionally) returns the given error, so can conveniently be
wrapped by `throw`: (throw (error! (ex-info ...)), etc.
Examples: Examples:

View file

@ -1,8 +1,10 @@
"Event" signal creator, emphasizing id + (optional level). "Event" signal creator, emphasizing id + (optional level).
Returns true iff signal was created (allowed by filtering).
Default kind: `:event` Default kind: `:event`
Default level: `:info` Default level: `:info`
Returns:
- For `event!` variant: nil, unconditionally.
- For `event!?` variant: true iff signal was created (allowed by filtering).
When filtering conditions are met [4], creates a Telemere signal [3] and When filtering conditions are met [4], creates a Telemere signal [3] and
dispatches it to registered handlers for processing (e.g. writing to dispatches it to registered handlers for processing (e.g. writing to

View file

@ -1,8 +1,10 @@
"Log" signal creator, emphasizing (optional level) + message. "Log" signal creator, emphasizing (optional level) + message.
Returns true iff signal was created (allowed by filtering).
Default kind: `:log` Default kind: `:log`
Default level: `:info` Default level: `:info`
Returns:
- For `log!` variant: nil, unconditionally.
- For `log!?` variant: true iff signal was created (allowed by filtering).
When filtering conditions are met [4], creates a Telemere signal [3] and When filtering conditions are met [4], creates a Telemere signal [3] and
dispatches it to registered handlers for processing (e.g. writing to dispatches it to registered handlers for processing (e.g. writing to

View file

@ -1,12 +1,11 @@
Low-level "generic" signal creator for creating signals of any "kind". Low-level "generic" signal creator for creating signals of any "kind".
Takes a single map of options [2] with compile-time keys. Takes a single map of options [2] with compile-time keys.
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 kind: `:generic` (feel free to change!)
Default level: `:info` Default level: `:info`
Returns:
- If given `:run` form: unconditionally returns run value, or rethrows run error.
- Otherwise: returns true iff signal was created (allowed by filtering).
When filtering conditions are met [4], creates a Telemere signal [3] and When filtering conditions are met [4], creates a Telemere signal [3] and
dispatches it to registered handlers for processing (e.g. writing to dispatches it to registered handlers for processing (e.g. writing to

View file

@ -14,13 +14,14 @@ various keys:
Creators vary only in in their default options and call APIs (expected args 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: and return values), making them more/less convenient for certain use cases:
`signal!` ------- opts => allowed? / unconditional run result (value or throw) `log!` ------------- ?level + msg => nil
`event!` -------- id + ?level => allowed? `event!` ----------- id + ?level => nil
`log!` ---------- ?level + msg => allowed? `trace!` ----------- ?id + run => run result (value or throw)
`trace!` -------- ?id + run => unconditional run result (value or throw) `spy!` ------------- ?level + run => run result (value or throw)
`spy!` ---------- ?level + run => unconditional run result (value or throw) `error!` ----------- ?id + error => given error
`error!` -------- ?id + error => unconditional given error `catch->error!` ---- ?id + run => run value or ?catch-val
`catch->error!` - ?id + run => unconditional run value or ?catch-val `uncaught->error!` - ?id => nil
`signal!` ---------- opts => allowed? / run result (value or throw)
- `log!` and `event!` are both good default/general-purpose signal creators. - `log!` and `event!` are both good default/general-purpose signal creators.
- `log!` emphasizes messages, while `event!` emphasizes ids. - `log!` emphasizes messages, while `event!` emphasizes ids.

View file

@ -1,8 +1,8 @@
"Spy" signal creator, emphasizing (optional level) + form to run. "Spy" signal creator, emphasizing (optional level) + form to run.
ALWAYS (unconditionally) returns run value, or rethrows run error.
Default kind: `:spy` Default kind: `:spy`
Default level: `:info` Default level: `:info`
Returns: ALWAYS (unconditionally) returns run value, or rethrows run error.
When filtering conditions are met [4], creates a Telemere signal [3] and When filtering conditions are met [4], creates a Telemere signal [3] and
dispatches it to registered handlers for processing (e.g. writing to dispatches it to registered handlers for processing (e.g. writing to

View file

@ -1,8 +1,8 @@
"Trace" signal creator, emphasizing (optional id) + form to run. "Trace" signal creator, emphasizing (optional id) + form to run.
ALWAYS (unconditionally) returns run value, or rethrows run error.
Default kind: `:trace` Default kind: `:trace`
Default level: `:info` (intentionally NOT `:trace`!) Default level: `:info` (intentionally NOT `:trace`!)
Returns: ALWAYS (unconditionally) returns run value, or rethrows run error.
When filtering conditions are met [4], creates a Telemere signal [3] and When filtering conditions are met [4], creates a Telemere signal [3] and
dispatches it to registered handlers for processing (e.g. writing to dispatches it to registered handlers for processing (e.g. writing to

View file

@ -197,14 +197,14 @@
(comment (enc/qb 1e6 (force *otel-tracer*))) ; 51.23 (comment (enc/qb 1e6 (force *otel-tracer*))) ; 51.23
;;;; Signal creators ;;;; Signal creators
;; - signal! ---------- opts => allowed? / unconditional run result (value or throw) ;; - log! ------------- ?level + msg => nil
;; - event! ----------- id + ?level => allowed? ;; - event! ----------- id + ?level => nil
;; - log! ------------- ?level + msg => allowed? ;; - trace! ----------- ?id + run => run result (value or throw)
;; - trace! ----------- ?id + run => unconditional run result (value or throw) ;; - spy! ------------- ?level + run => run result (value or throw)
;; - spy! ------------- ?level + run => unconditional run result (value or throw) ;; - error! ----------- ?id + error => given error
;; - error! ----------- ?id + error => unconditional given error ;; - catch->error! ---- ?id + run => run value or ?catch-val
;; - catch->error! ---- ?id + run => unconditional run value or ?catch-val
;; - uncaught->error! - ?id => nil ;; - uncaught->error! - ?id => nil
;; - signal! ---------- opts => allowed? / run result (value or throw)
#?(:clj #?(:clj
(defn- args->opts [args] (defn- args->opts [args]
@ -251,28 +251,46 @@
(merge m v) (merge m v)
(assoc m k 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 :id opts-or-id)))
([id opts-or-level] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :level opts-or-level) :id id))))))
(comment (:coords (with-signal (event! ::my-id :info))))
#?(:clj #?(:clj
(let [base-opts {:kind :log, :level :info}] (let [base-opts {:kind :log, :level :info}]
(defmacro log! (defmacro log!?
"?level + msg => allowed?" "?level + msg => allowed?"
{:doc (impl/signal-docstring :log!) {:doc (impl/signal-docstring :log!)
:arglists (impl/signal-arglists :log!)} :arglists (impl/signal-arglists :log!)}
([opts-or-msg ] `(impl/signal! ~(merge-or-assoc-opts base-opts &form :msg opts-or-msg))) ([opts-or-msg ] `(impl/signal! ~(merge-or-assoc-opts base-opts &form :msg opts-or-msg)))
([opts-or-level msg] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :level opts-or-level) :msg msg)))))) ([opts-or-level msg] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :level opts-or-level) :msg msg))))))
(comment (:coords (with-signal (log!? :info "My msg"))))
#?(:clj
(defmacro log!
"Like `log!?` but always returns nil."
{:doc (impl/signal-docstring :log!)
:arglists (impl/signal-arglists :log!)}
[& args] `(do ~(truss/keep-callsite `(log!? ~@args)) nil)))
(comment (:coords (with-signal (log! :info "My msg")))) (comment (:coords (with-signal (log! :info "My msg"))))
#?(: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 :id opts-or-id)))
([id opts-or-level] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :level opts-or-level) :id id))))))
(comment (:coords (with-signal (event!? ::my-id :info))))
#?(:clj
(defmacro event!
"Like `event!?` but always returns nil."
{:doc (impl/signal-docstring :event!)
:arglists (impl/signal-arglists :event!)}
[& args] `(do ~(truss/keep-callsite `(event!? ~@args)) nil)))
(comment (:coords (with-signal (event! ::my-id :info))))
#?(:clj #?(:clj
(let [base-opts {:kind :trace, :level :info, :msg `impl/default-trace-msg}] (let [base-opts {:kind :trace, :level :info, :msg `impl/default-trace-msg}]
(defmacro trace! (defmacro trace!

View file

@ -405,16 +405,6 @@
sample-rate kind ns id level when rate-limit rate-limit-by, sample-rate kind ns id level when rate-limit rate-limit-by,
ctx ctx+ parent root trace?, do let data msg error run & kvs]}]) ctx ctx+ parent root trace?, do let data msg error run & kvs]}])
:event! ; id + ?level => allowed?
'([opts-or-id]
[id level]
[id
{:as opts-map :keys
[#_elide? #_allow? #_callsite-id,
elidable? coords 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! ; ?level + msg => allowed? :log! ; ?level + msg => allowed?
'([opts-or-msg] '([opts-or-msg]
[level msg] [level msg]
@ -425,6 +415,16 @@
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]} ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
msg]) msg])
:event! ; id + ?level => allowed?
'([opts-or-id]
[id level]
[id
{:as opts-map :keys
[#_elide? #_allow? #_callsite-id,
elidable? coords 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]}])
:trace! ; ?id + run => unconditional run result (value or throw) :trace! ; ?id + run => unconditional run result (value or throw)
'([opts-or-run] '([opts-or-run]
[id run] [id run]
@ -532,7 +532,7 @@
#?(:clj #?(:clj
(defmacro signal! (defmacro signal!
"Generic low-level signal creator. Wrapped for public API." "Generic low-level signal creator. Wrapped for public API."
([ opts] (truss/keep-callsite `(signal!? nil ~opts))) ([ opts] (truss/keep-callsite `(signal! nil ~opts)))
([base-opts opts] ([base-opts opts]
(valid-opts! (or base-opts {})) (valid-opts! (or base-opts {}))
(valid-opts! (or opts {})) (valid-opts! (or opts {}))

View file

@ -574,19 +574,19 @@
;;;; ;;;;
(deftest _common-signals (deftest _common-signals
[(testing "event!" ; id + ?level => allowed? [(testing "event!?" ; id + ?level => allowed?
[(let [{rv :value, [sv] :signals} (with-sigs (tel/event! :id1 ))] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :info, :id :id1}))]) [(let [{rv :value, [sv] :signals} (with-sigs (tel/event!? :id1 ))] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :info, :id :id1}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/event! :id1 :warn)) ] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :warn, :id :id1}))]) (let [{rv :value, [sv] :signals} (with-sigs (tel/event!? :id1 :warn)) ] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :warn, :id :id1}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/event! :id1 {:level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :warn, :id :id1}))]) (let [{rv :value, [sv] :signals} (with-sigs (tel/event!? :id1 {:level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :warn, :id :id1}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/event! {:id :id1, :level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :warn, :id :id1}))]) (let [{rv :value, [sv] :signals} (with-sigs (tel/event!? {:id :id1, :level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :warn, :id :id1}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/event! :id1 {:allow? false})) ] [(is (= rv nil)) (is (nil? sv))])]) (let [{rv :value, [sv] :signals} (with-sigs (tel/event!? :id1 {:allow? false})) ] [(is (= rv nil)) (is (nil? sv))])])
(testing "log!" ; ?level + msg => allowed? (testing "log!?" ; ?level + msg => allowed?
[(let [{rv :value, [sv] :signals} (with-sigs (tel/log! "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :info}))]) [(let [{rv :value, [sv] :signals} (with-sigs (tel/log!? "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :info}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/log! :warn "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :warn}))]) (let [{rv :value, [sv] :signals} (with-sigs (tel/log!? :warn "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :warn}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/log! {:level :warn} "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :warn}))]) (let [{rv :value, [sv] :signals} (with-sigs (tel/log!? {:level :warn} "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :warn}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/log! {:level :warn, :msg "msg"}))] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :warn}))]) (let [{rv :value, [sv] :signals} (with-sigs (tel/log!? {:level :warn, :msg "msg"}))] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :warn}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/log! {:allow? false} "msg")) ] [(is (= rv nil)) (is (nil? sv))])]) (let [{rv :value, [sv] :signals} (with-sigs (tel/log!? {:allow? false} "msg")) ] [(is (= rv nil)) (is (nil? sv))])])
(testing "trace!" ; ?id + run => unconditional run result (value or throw) (testing "trace!" ; ?id + run => unconditional run result (value or throw)
[(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id nil, :msg_ "(+ 1 2) => 3"}))]) [(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id nil, :msg_ "(+ 1 2) => 3"}))])

View file

@ -131,13 +131,13 @@ Use whichever signal creator is most convenient for your needs:
| Name | Kind | Args | Returns | | Name | Kind | Args | Returns |
| :---------------------------------------------------------------------------------------------------------- | :--------- | :--------------- | :--------------------------- | | :---------------------------------------------------------------------------------------------------------- | :--------- | :--------------- | :--------------------------- |
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts | | [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `:log` | `?level` + `msg` | nil |
| [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `:event` | `id` + `?level` | Signal allowed? | | [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `:event` | `id` + `?level` | nil |
| [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `:log` | `?level` + `msg` | Signal allowed? |
| [`trace!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#trace!) | `:trace` | `?id` + `run` | Form result | | [`trace!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#trace!) | `:trace` | `?id` + `run` | Form result |
| [`spy!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#spy!) | `:spy` | `?level` + `run` | Form result | | [`spy!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#spy!) | `:spy` | `?level` + `run` | Form result |
| [`error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#error!) | `:error` | `?id` + `error` | Given error | | [`error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#error!) | `:error` | `?id` + `error` | Given error |
| [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) | `:error` | `?id` | Form value or given fallback | | [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) | `:error` | `?id` | Form value or given fallback |
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts |
- See relevant docstrings (links above) for usage info. - See relevant docstrings (links above) for usage info.
- See [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) for more about signal creators. - See [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) for more about signal creators.