[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 |
| :---------------------------------------------------------------------------------------------------------- | :--------- | :--------------- | :--------------------------- |
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts |
| [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `:event` | `id` + `?level` | Signal allowed? |
| [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `:log` | `?level` + `msg` | Signal allowed? |
| [`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` | nil |
| [`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 |
| [`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 |
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts |
### Internal help

View file

@ -1,11 +1,12 @@
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 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`.
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.).
ALWAYS (unconditionally) returns the given error, so can conveniently be wrapped
by `throw`: (throw (error! (ex-info ...)), etc.
Default kind: `:error`
Default level: `:error`
Returns:
ALWAYS (unconditionally) returns the given error, so can conveniently be
wrapped by `throw`: (throw (error! (ex-info ...)), etc.
Examples:

View file

@ -1,8 +1,10 @@
"Event" signal creator, emphasizing id + (optional level).
Returns true iff signal was created (allowed by filtering).
Default kind: `:event`
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
dispatches it to registered handlers for processing (e.g. writing to

View file

@ -1,8 +1,10 @@
"Log" signal creator, emphasizing (optional level) + message.
Returns true iff signal was created (allowed by filtering).
Default kind: `:log`
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
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".
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 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
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
and return values), making them more/less convenient for certain use cases:
`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!` ------------- ?level + msg => nil
`event!` ----------- id + ?level => nil
`trace!` ----------- ?id + run => run result (value or throw)
`spy!` ------------- ?level + run => run result (value or throw)
`error!` ----------- ?id + error => given error
`catch->error!` ---- ?id + run => 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!` emphasizes messages, while `event!` emphasizes ids.

View file

@ -1,8 +1,8 @@
"Spy" signal creator, emphasizing (optional level) + form to run.
ALWAYS (unconditionally) returns run value, or rethrows run error.
Default kind: `:spy`
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
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.
ALWAYS (unconditionally) returns run value, or rethrows run error.
Default kind: `: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
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
;;;; Signal creators
;; - 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! ------------- ?level + msg => nil
;; - event! ----------- id + ?level => nil
;; - trace! ----------- ?id + run => run result (value or throw)
;; - spy! ------------- ?level + run => run result (value or throw)
;; - error! ----------- ?id + error => given error
;; - catch->error! ---- ?id + run => run value or ?catch-val
;; - uncaught->error! - ?id => nil
;; - signal! ---------- opts => allowed? / run result (value or throw)
#?(:clj
(defn- args->opts [args]
@ -251,28 +251,46 @@
(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 :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
(let [base-opts {:kind :log, :level :info}]
(defmacro log!
(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 :msg opts-or-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"))))
#?(: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
(let [base-opts {:kind :trace, :level :info, :msg `impl/default-trace-msg}]
(defmacro trace!

View file

@ -405,16 +405,6 @@
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 + ?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?
'([opts-or-msg]
[level msg]
@ -425,6 +415,16 @@
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
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)
'([opts-or-run]
[id run]
@ -532,7 +532,7 @@
#?(:clj
(defmacro signal!
"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]
(valid-opts! (or base-opts {}))
(valid-opts! (or opts {}))

View file

@ -574,19 +574,19 @@
;;;;
(deftest _common-signals
[(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 :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! :id1 {:allow? false})) ] [(is (= rv nil)) (is (nil? sv))])])
[(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 :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!? :id1 {:allow? false})) ] [(is (= rv nil)) (is (nil? sv))])])
(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! :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! {:allow? false} "msg")) ] [(is (= rv nil)) (is (nil? sv))])])
(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!? :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!? {:allow? false} "msg")) ] [(is (= rv nil)) (is (nil? sv))])])
(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"}))])

View file

@ -131,13 +131,13 @@ Use whichever signal creator is most convenient for your needs:
| Name | Kind | Args | Returns |
| :---------------------------------------------------------------------------------------------------------- | :--------- | :--------------- | :--------------------------- |
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts |
| [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `:event` | `id` + `?level` | Signal allowed? |
| [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `:log` | `?level` + `msg` | Signal allowed? |
| [`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` | nil |
| [`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 |
| [`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 |
| [`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 [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) for more about signal creators.