mirror of
https://github.com/taoensso/telemere.git
synced 2025-12-16 17:41:12 +00:00
[doc] Doc and example improvements
This commit is contained in:
parent
3b6396426e
commit
946240dda4
19 changed files with 362 additions and 198 deletions
66
README.md
66
README.md
|
|
@ -38,7 +38,7 @@ See [here][GitHub releases] for earlier releases.
|
|||
|
||||
- Hyper-optimized and **blazing fast**, see [benchmarks](#benchmarks).
|
||||
- An API that **scales comfortably** from the smallest disposable code, to the most massive and complex real-world production environments.
|
||||
- Auto [handler stats](https://cljdoc.org/d/com.taoensso/telemere/1.0.0-beta13/api/taoensso.telemere#get-handlers-stats) for debugging performance and other issues at scale.
|
||||
- Auto [handler stats](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats) for debugging performance and other issues at scale.
|
||||
|
||||
#### Flexibility
|
||||
|
||||
|
|
@ -48,6 +48,8 @@ See [here][GitHub releases] for earlier releases.
|
|||
- Fully [configurable](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) **a/sync dispatch support**: blocking, dropping, sliding, etc.
|
||||
- Turn-key **sampling**, **rate-limiting**, and **back-pressure monitoring** with sensible defaults.
|
||||
|
||||
> A comparison to the excellent [Mulog](https://github.com/BrunoBonacci/mulog) micro-logging library is provided [here](../../wiki/6-FAQ#how-does-telemere-compare-to-mulog).
|
||||
|
||||
## Video demo
|
||||
|
||||
See for intro and basic usage:
|
||||
|
|
@ -66,11 +68,11 @@ See for intro and basic usage:
|
|||
;; 2024-04-11T10:54:57.202869Z INFO LOG Schrebermann.local examples(56,1) ::my-id - My message
|
||||
;; data: {:x1 :x2}
|
||||
|
||||
(t/log! "This will send a `:log` signal to the Clj/s console")
|
||||
(t/log! "This will send a `:log` signal to the Clj/s console")
|
||||
(t/log! :info "This will do the same, but only when the current level is >= `:info`")
|
||||
|
||||
;; Easily construct messages
|
||||
(let [x "constructed"] (t/log! :info ["Here's a" x "message!"]))
|
||||
;; Easily construct messages from parts
|
||||
(t/log! :info ["Here's a" "joined" "message!"])
|
||||
|
||||
;; Attach an id
|
||||
(t/log! {:level :info, :id ::my-id} "This signal has an id")
|
||||
|
|
@ -82,8 +84,8 @@ See for intro and basic usage:
|
|||
(t/with-signal (t/log! "This will be captured"))
|
||||
;; => {:keys [location level id data msg_ ...]}
|
||||
|
||||
;; `:let` bindings available to `:data` and message, only paid for
|
||||
;; when allowed by minimum level and other filtering criteria
|
||||
;; `:let` bindings are available to `:data` and message, but only paid
|
||||
;; for when allowed by minimum level and other filtering criteria
|
||||
(t/log!
|
||||
{:level :info
|
||||
:let [expensive-metric1 (last (for [x (range 100), y (range 100)] (* x y)))]
|
||||
|
|
@ -96,19 +98,49 @@ See for intro and basic usage:
|
|||
:rate-limit {"1 per sec" [1 1000]}}
|
||||
"This signal will be sampled and rate limited")
|
||||
|
||||
;;; A quick taste of filtering...
|
||||
;; There are several signal creators available for convenience.
|
||||
;; All support the same options but each offer's a calling API
|
||||
;; optimized for a particular use case. Compare:
|
||||
|
||||
(t/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"}) ; Set namespace filter
|
||||
(t/set-id-filter! {:allow #{::my-particular-id "my-app/*"}}) ; Set id filter
|
||||
;; `log!` - [msg] or [level-or-opts msg]
|
||||
(t/with-signal (t/log! {:level :info, :id ::my-id} "Hi!"))
|
||||
|
||||
(t/set-min-level! :warn) ; Set minimum level
|
||||
(t/set-min-level! :log :debug) ; Set minimul level for `:log` signals
|
||||
;; `event!` - [id] or [id level-or-opts]
|
||||
(t/with-signal (t/event! ::my-id {:level :info, :msg "Hi!"}))
|
||||
|
||||
;; Set minimum level for `:event` signals originating in the "taoensso.sente.*" ns
|
||||
;; `signal!` - [opts]
|
||||
(t/with-signal (t/signal! {:level :info, :id ::my-id, :msg "Hi!"}))
|
||||
|
||||
;; See `t/help:signal-creators` docstring for more
|
||||
|
||||
;;; A quick taste of filtering
|
||||
|
||||
(t/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
|
||||
(t/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})
|
||||
|
||||
(t/set-min-level! :warn) ; Set minimum level for all signals
|
||||
(t/set-min-level! :log :debug) ; Set minimul level for `log!` signals
|
||||
|
||||
;; Set minimum level for `event!` signals originating in
|
||||
;; the "taoensso.sente.*" ns
|
||||
(t/set-min-level! :event "taoensso.sente.*" :warn)
|
||||
|
||||
;; See `t/help:filters` docstring for more
|
||||
|
||||
;;; Use middleware to transform signals and/or filter signals
|
||||
;;; by signal data/content/etc.
|
||||
|
||||
(t/set-middleware!
|
||||
(fn [signal]
|
||||
(if (get-in signal [:data :hide-me?])
|
||||
nil ; Suppress signal (don't handle)
|
||||
(assoc signal :passed-through-middleware? true))))
|
||||
|
||||
(t/with-signal (t/event! ::my-id {:data {:hide-me? true}})) ; => nil
|
||||
(t/with-signal (t/event! ::my-id {:data {:hide-me? false}})) ; => {...}
|
||||
```
|
||||
|
||||
See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cljc) for more REPL-ready snippets.
|
||||
See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cljc) for more REPL-ready snippets!
|
||||
|
||||
## API overview
|
||||
|
||||
|
|
@ -128,15 +160,15 @@ See relevant docstrings (links below) for usage info-
|
|||
|
||||
### Internal help
|
||||
|
||||
Help is available without leaving your IDE:
|
||||
Detailed help is available without leaving your IDE:
|
||||
|
||||
| Var | Help with |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------ |
|
||||
| [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) | Creating signals |
|
||||
| [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Signal options |
|
||||
| [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Options when creating signals |
|
||||
| [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) | Signal content (map given to middleware/handlers) |
|
||||
| [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) | Signal and handler filters |
|
||||
| [`help:handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handlers) | Signal handlers |
|
||||
| [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) | Signal filtering and transformation |
|
||||
| [`help:handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handlers) | Signal handler management |
|
||||
| [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) | Signal handler dispatch options |
|
||||
| [`help:environmental-config`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:environmental-config) | Config via JVM properties, environment variables, or classpath resources. |
|
||||
|
||||
|
|
|
|||
147
examples.cljc
147
examples.cljc
|
|
@ -6,25 +6,28 @@
|
|||
|
||||
(require '[taoensso.telemere :as t])
|
||||
|
||||
;; Start simple
|
||||
(t/log! "This will send a `:log` signal to the Clj/s console")
|
||||
(t/log! {:id ::my-id, :data {:x1 :x2}} "My message") ; %>
|
||||
;; 2024-04-11T10:54:57.202869Z INFO LOG Schrebermann.local examples(56,1) ::my-id - My message
|
||||
;; data: {:x1 :x2}
|
||||
|
||||
(t/log! "This will send a `:log` signal to the Clj/s console")
|
||||
(t/log! :info "This will do the same, but only when the current level is >= `:info`")
|
||||
|
||||
;; Easily construct messages
|
||||
(let [x "constructed"] (t/log! :info ["Here's a" x "message!"]))
|
||||
;; Easily construct messages from parts
|
||||
(t/log! :info ["Here's a" "joined" "message!"])
|
||||
|
||||
;; Attach an id
|
||||
(t/log! {:level :info, :id ::my-id} "This signal has an id")
|
||||
|
||||
;; Attach arb user data
|
||||
;; Attach arb data
|
||||
(t/log! {:level :info, :data {:x :y}} "This signal has structured data")
|
||||
|
||||
;; Capture for debug/testing
|
||||
(t/with-signal (t/log! "This will be captured"))
|
||||
;; => {:keys [location level id data msg_ ...]}
|
||||
|
||||
;; `:let` bindings available to `:data` and message, only paid for
|
||||
;; when allowed by minimum level and other filtering criteria
|
||||
;; `:let` bindings are available to `:data` and message, but only paid
|
||||
;; for when allowed by minimum level and other filtering criteria
|
||||
(t/log!
|
||||
{:level :info
|
||||
:let [expensive-metric1 (last (for [x (range 100), y (range 100)] (* x y)))]
|
||||
|
|
@ -37,22 +40,46 @@
|
|||
:rate-limit {"1 per sec" [1 1000]}}
|
||||
"This signal will be sampled and rate limited")
|
||||
|
||||
;;; A quick taste of filtering...
|
||||
;; There are several signal creators available for convenience.
|
||||
;; All support the same options but each offer's a calling API
|
||||
;; optimized for a particular use case. Compare:
|
||||
|
||||
;; `log!` - [msg] or [level-or-opts msg]
|
||||
(t/with-signal (t/log! {:level :info, :id ::my-id} "Hi!"))
|
||||
|
||||
;; `event!` - [id] or [id level-or-opts]
|
||||
(t/with-signal (t/event! ::my-id {:level :info, :msg "Hi!"}))
|
||||
|
||||
;; `signal!` - [opts]
|
||||
(t/with-signal (t/signal! {:level :info, :id ::my-id, :msg "Hi!"}))
|
||||
|
||||
;; See `t/help:signal-creators` docstring for more
|
||||
|
||||
;;; A quick taste of filtering
|
||||
|
||||
(t/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"}) ; Set namespace filter
|
||||
(t/set-id-filter! {:allow #{::my-particular-id "my-app/*"}}) ; Set id filter
|
||||
|
||||
(t/set-min-level! :warn) ; Set minimum level
|
||||
(t/set-min-level! :warn) ; Set minimum level for all signals
|
||||
(t/set-min-level! :log :debug) ; Set minimul level for `log!` signals
|
||||
|
||||
;; Set minimum level for `event!` signals originating in the "taoensso.sente.*" ns
|
||||
;; Set minimum level for `event!` signals originating in
|
||||
;; the "taoensso.sente.*" ns
|
||||
(t/set-min-level! :event "taoensso.sente.*" :warn)
|
||||
|
||||
;;; Example handler output
|
||||
;; See `t/help:filters` docstring for more
|
||||
|
||||
(t/log! {:id ::my-id, :data {:x1 :x2}} "My message") ; =>
|
||||
;; 2024-04-11T10:54:57.202869Z INFO LOG Schrebermann.local examples(56,1) ::my-id - My message
|
||||
;; data: {:x1 :x2}
|
||||
;;; Use middleware to transform signals and/or filter signals
|
||||
;;; by signal data/content/etc.
|
||||
|
||||
(t/set-middleware!
|
||||
(fn [signal]
|
||||
(if (get-in signal [:data :hide-me?])
|
||||
nil ; Suppress signal (don't handle)
|
||||
(assoc signal :passed-through-middleware? true))))
|
||||
|
||||
(t/with-signal (t/event! ::my-id {:data {:hide-me? true}})) ; => nil
|
||||
(t/with-signal (t/event! ::my-id {:data {:hide-me? false}})) ; => {...}
|
||||
|
||||
;;;; Docstring snippets
|
||||
|
||||
|
|
@ -134,19 +161,19 @@
|
|||
(def my-handler (t/handler:console))
|
||||
|
||||
;; Test handler, remember it's just a (fn [signal])
|
||||
(my-handler my-signal) ; =>
|
||||
(my-handler my-signal) ; %>
|
||||
;; 2024-04-11T10:54:57.202869Z INFO LOG Schrebermann.local examples(56,1) ::my-id - My message
|
||||
;; data: {:x1 :x2}
|
||||
|
||||
;; Create console which writes signals as edn
|
||||
;; Create console handler which writes signals as edn
|
||||
(def my-handler
|
||||
(t/handler:console
|
||||
{:output-fn (t/pr-signal-fn {:pr-fn :edn})}))
|
||||
|
||||
(my-handler my-signal) ; =>
|
||||
(my-handler my-signal) ; %>
|
||||
;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...}
|
||||
|
||||
;; Create console which writes signals as JSON
|
||||
;; Create console handler which writes signals as JSON
|
||||
#?(:clj (require '[jsonista.core :as jsonista]))
|
||||
(def my-handler
|
||||
(t/handler:console
|
||||
|
|
@ -156,17 +183,42 @@
|
|||
#?(:cljs :json
|
||||
:clj jsonista.core/write-value-as-string)})}))
|
||||
|
||||
(my-handler my-signal) ; =>
|
||||
(my-handler my-signal) ; %>
|
||||
;; {"inst":"2024-04-11T10:54:57.202869Z","msg_":"My message","ns":"examples", ...}
|
||||
|
||||
;; Deregister the default console handler
|
||||
(t/remove-handler! :default/console)
|
||||
|
||||
;; Register our custom console handler
|
||||
(t/add-handler! :my-handler my-handler
|
||||
;; Lots of options here for filtering, etc.
|
||||
;; See `help:handler-dispatch-options` docstring!
|
||||
{})
|
||||
|
||||
;; NB make sure to always stop handlers at the end of your
|
||||
;; `-main` or shutdown procedure
|
||||
(t/call-on-shutdown! t/stop-handlers!)
|
||||
|
||||
;; See `t/help:handlers` docstring for more
|
||||
|
||||
;;; Writing handlers
|
||||
|
||||
(defn handler:my-handler ; Note naming convention
|
||||
;; Handlers are just fns of 2 arities
|
||||
|
||||
(defn my-basic-handler
|
||||
([signal] (println signal)) ; Arity-1 called when handling a signal
|
||||
([]) ; Arity-0 called when stopping the handler
|
||||
)
|
||||
|
||||
;; If you're making a customizable handler for use by others, it's often
|
||||
;; handy to define a handler constructor
|
||||
|
||||
(defn handler:my-fancy-handler ; Note constructor naming convention
|
||||
"Needs `some-lib`, Ref. <https://github.com/example/some-lib>.
|
||||
|
||||
Returns a (fn handler [signal] that:
|
||||
Returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Does something with the signal.
|
||||
- Does something useful with the signal!
|
||||
|
||||
Options:
|
||||
`:option1` - Option description
|
||||
|
|
@ -176,36 +228,35 @@
|
|||
- Tip 1
|
||||
- Tip 2"
|
||||
|
||||
([] (handler:my-handler nil)) ; Use default opts (when defaults viable)
|
||||
([] (handler:my-fancy-handler nil)) ; Use default opts (iff defaults viable)
|
||||
([{:as constructor-opts}]
|
||||
|
||||
;; Do option validation and other prep here, i.e. try to keep expensive work
|
||||
;; outside handler function when possible.
|
||||
;; Do option validation and other prep here, i.e. try to keep
|
||||
;; expensive work outside handler function when possible!
|
||||
|
||||
(let [handler-fn
|
||||
(fn a-handler:my-handler ; Note naming convention
|
||||
(let [handler-fn ; Fn of exactly 2 arities
|
||||
(fn a-handler:my-fancy-handler ; Note fn naming convention
|
||||
|
||||
;; Main arity, called by Telemere when handler should process given signal
|
||||
([signal]
|
||||
;; Do something with given signal (write to console/file/queue/db, etc.).
|
||||
;; Return value is ignored.
|
||||
([signal] ; Arity-1 called when handling a signal
|
||||
;; Do something useful with the given signal (write to
|
||||
;; console/file/queue/db, etc.). Return value is ignored.
|
||||
)
|
||||
|
||||
;; Optional stop arity for handlers that need to close/release resources or
|
||||
;; otherwise finalize themselves during system shutdown, etc. Called by
|
||||
;; Telemere when appropriate, but ONLY IF the handler's dispatch options
|
||||
;; include a truthy `:needs-stopping?` value (false by default).
|
||||
([]
|
||||
;; Close/release resources, etc.
|
||||
))
|
||||
([] ; Arity-0 called when stopping the handler
|
||||
;; Flush buffers, close files, etc. May just noop.
|
||||
;; Return value is ignored.
|
||||
))]
|
||||
|
||||
;; (Advanced) optional default handler dispatch opts,
|
||||
;; see `add-handler!` for full list of possible opts
|
||||
default-dispatch-opts
|
||||
{:min-level :info
|
||||
:rate-limit [[1 (enc/msecs :min 1)]]}]
|
||||
;; (Advanced, optional) You can use metadata to provide default
|
||||
;; handler dispatch options (see `help:handler-dispatch-options`)
|
||||
|
||||
(with-meta handler-fn default-dispatch-opts))))
|
||||
(with-meta handler-fn
|
||||
{:dispatch-opts
|
||||
{:min-level :info
|
||||
:rate-limit
|
||||
[[1 1000] ; Max 1 signal per second
|
||||
[10 60000] ; Max 10 signals per minute
|
||||
]}}))))
|
||||
|
||||
;;; Message building
|
||||
|
||||
|
|
@ -240,7 +291,7 @@
|
|||
(t/log! (format "This message was built by `%s`" "format"))
|
||||
;; %> {:msg "This message was built by `format`"}
|
||||
|
||||
;;; User kvs
|
||||
;;; App-level kvs
|
||||
|
||||
(t/with-signal
|
||||
(t/event! ::my-id
|
||||
|
|
@ -248,10 +299,10 @@
|
|||
:my-handler-data "bar"}))
|
||||
|
||||
;; %>
|
||||
;; {;; User kvs included inline (assoc'd to signal root)
|
||||
;; {;; App-level kvs included inline (assoc'd to signal root)
|
||||
;; :my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"
|
||||
;; :kvs ; And also collected together under ":kvs" key
|
||||
;; {:my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"}
|
||||
;; {:my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"}
|
||||
;; ... }
|
||||
|
|
|
|||
|
|
@ -6,20 +6,20 @@ Default signal keys:
|
|||
`:schema` ------ Int version of signal schema (current: 1)
|
||||
`:inst` -------- Platform instant [1] when signal was created
|
||||
`:level` ------- Signal level ∈ #{<int> :trace :debug :info :warn :error :fatal :report ...}
|
||||
`:kind` -------- Signal ?kind ∈ #{nil :event :error :log :trace :spy :slf4j :tools-logging <user-val> ...}
|
||||
`:kind` -------- Signal ?kind ∈ #{nil :event :error :log :trace :spy :slf4j :tools-logging <app-val> ...}
|
||||
`:id` ---------- ?id of signal (common to all signals created at callsite, contrast with `:uid`)
|
||||
`:uid` --------- ?id of signal instance (unique to each signal created at callsite, contrast with `:id`)
|
||||
|
||||
`:msg` --------- Arb user-level message ?str given to signal creator
|
||||
`:data` -------- Arb user-level data ?val (usu. a map) given to signal creator
|
||||
`:error` ------- Arb user-level platform ?error [2] given to signal creator
|
||||
`:msg` --------- Arb app-level message ?str given to signal creator
|
||||
`:data` -------- Arb app-level data ?val (usu. a map) given to signal creator
|
||||
`:error` ------- Arb app-level platform ?error [2] given to signal creator
|
||||
|
||||
`:run-form` ---- Unevaluated ?form given to signal creator as `:run`
|
||||
`:run-val` ----- Successful return ?val of `:run` ?form
|
||||
`:run-nsecs` --- ?int nanosecs runtime of `:run` ?form
|
||||
`:end-inst` ---- Platform ?instant [1] when `:run` ?form completed
|
||||
|
||||
`:ctx` --------- ?val of `*ctx*` (arb user-level state) when signal was created
|
||||
`:ctx` --------- ?val of `*ctx*` (arb app-level state) when signal was created
|
||||
`:parent` ------ ?{:keys [id uid]} of parent signal, present in nested signals when tracing
|
||||
`:location` ---- ?{:keys [ns file line column]} signal creator callsite
|
||||
`:ns` ---------- ?str namespace of signal creator callsite, same as (:ns location)
|
||||
|
|
@ -29,7 +29,7 @@ Default signal keys:
|
|||
`:thread` ------ (Clj only) {:keys [group name id]} thread info for thread that called signal creator
|
||||
`:sample-rate` - ?rate ∈ℝ[0,1] for combined signal AND handler sampling (0.75 => allow 75% of signals, nil => allow all)
|
||||
|
||||
<kvs> ---------- Other arb user-level ?kvs given to signal creator. Typically NOT included
|
||||
<kvs> ---------- Other arb app-level ?kvs given to signal creator. Typically NOT included
|
||||
in handler output, so a great way to provide custom data/opts for use
|
||||
(only) by custom middleware/handlers.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
Signal options (shared by all signal creators):
|
||||
|
||||
`:inst` -------- Platform instant [1] when signal was created, ∈ #{nil :auto <user-val>}
|
||||
`:inst` -------- Platform instant [1] when signal was created, ∈ #{nil :auto <[1]>}
|
||||
`:level` ------- Signal level ∈ #{<int> :trace :debug :info :warn :error :fatal :report ...}
|
||||
`:kind` -------- Signal ?kind ∈ #{nil :event :error :log :trace :spy <user-val> ...}
|
||||
`:kind` -------- Signal ?kind ∈ #{nil :event :error :log :trace :spy <app-val> ...}
|
||||
`:id` ---------- ?id of signal (common to all signals created at callsite, contrast with `:uid`)
|
||||
`:uid` --------- ?id of signal instance (unique to each signal created at callsite, contrast with `:id`)
|
||||
|
||||
`:msg` --------- Arb user-level ?message to incl. in signal: str or vec of strs to join (with `\space`)
|
||||
`:data` -------- Arb user-level ?data to incl. in signal: usu. a map
|
||||
`:error` ------- Arb user-level ?error to incl. in signal: platform error [2]
|
||||
`:msg` --------- Arb app-level ?message to incl. in signal: str or vec of strs to join (with `\space`)
|
||||
`:data` -------- Arb app-level ?data to incl. in signal: usu. a map
|
||||
`:error` ------- Arb app-level ?error to incl. in signal: platform error [2]
|
||||
|
||||
`:run` --------- ?form to execute UNCONDITIONALLY; will incl. `:run-value` in signal
|
||||
`:do` ---------- ?form to execute conditionally (iff signal allowed), before establishing `:let` ?binding
|
||||
|
|
@ -25,7 +25,7 @@ Signal options (shared by all signal creators):
|
|||
`:middleware` -- Optional (fn [signal]) => ?modified-signal to apply when signal is created
|
||||
`:trace?` ------ Should tracing be enabled for `:run` form?
|
||||
|
||||
<kvs> ---------- Other arb user-level ?kvs to incl. in signal. Typically NOT included in
|
||||
<kvs> ---------- Other arb app-level ?kvs to incl. in signal. Typically NOT included in
|
||||
handler output, so a great way to provide custom data/opts for use
|
||||
(only) by custom middleware/handlers.
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
#?(:clj enc/set-var-root!)
|
||||
#?(:clj enc/update-var-root!)
|
||||
#?(:clj enc/get-env)
|
||||
#?(:clj enc/call-on-shutdown!)
|
||||
enc/chance
|
||||
enc/rate-limiter
|
||||
enc/newline
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
(defn ^:public handler:console
|
||||
"Experimental, subject to change.
|
||||
|
||||
Returns a (fn handler [signal]) that:
|
||||
Returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Writes the signal as a string to specified stream.
|
||||
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
(defn ^:public handler:console
|
||||
"Experimental, subject to change.
|
||||
|
||||
If `js/console` exists, returns a (fn handler [signal]) that:
|
||||
If `js/console` exists, returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Writes the signal as a string to JavaScript console.
|
||||
|
||||
|
|
@ -89,7 +89,7 @@
|
|||
(defn ^:public handler:console-raw
|
||||
"Experimental, subject to change.
|
||||
|
||||
If `js/console` exists, returns a (fn handler [signal]) that:
|
||||
If `js/console` exists, returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Writes the raw signal to JavaScript console.
|
||||
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@
|
|||
(defn ^:public handler:file
|
||||
"Experimental, subject to change.
|
||||
|
||||
Returns a (fn handler [signal]) that:
|
||||
Returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Writes (appends) the signal as a string to file specified by `path`.
|
||||
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@
|
|||
Needs `opentelemetry-java`,
|
||||
Ref. <https://github.com/open-telemetry/opentelemetry-java>.
|
||||
|
||||
Returns a (fn handler [signal]) that:
|
||||
Returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Emits the signal to `io.opentelemetry.api.logs.Logger` returned
|
||||
by given `io.opentelemetry.api.logs.LoggerProvider`.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
|
||||
Needs `postal`, Ref. <https://github.com/drewr/postal>.
|
||||
|
||||
Returns a (fn handler [signal]) that:
|
||||
Returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Sends the signal as an email to specified recipient.
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
Needs `clj-slack`, Ref. <https://github.com/julienXX/clj-slack>.
|
||||
|
||||
Returns a (fn handler [signal]) that:
|
||||
Returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Writes the signal as a string to specified Slack channel.
|
||||
|
||||
|
|
@ -42,6 +42,7 @@
|
|||
]
|
||||
|
||||
Options:
|
||||
`:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
|
||||
`:conn-opts` - Map of connection opts given to `clj-slack.chat/post-message`
|
||||
Examples:
|
||||
{:token \"MY-TOKEN\"}
|
||||
|
|
@ -51,8 +52,6 @@
|
|||
Examples:
|
||||
{:channel-id \"C12345678\", :username \"MY_BOT\"}
|
||||
|
||||
`:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
|
||||
|
||||
Tips:
|
||||
- See `clj-slack` docs for more info on its options."
|
||||
|
||||
|
|
|
|||
|
|
@ -17,21 +17,20 @@
|
|||
(defn handler:tcp-socket
|
||||
"Experimental, subject to change.
|
||||
|
||||
Returns a (fn handler [signal]) that:
|
||||
Returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Sends the signal as a string to specified TCP socket.
|
||||
|
||||
Can output signals as human or machine-readable (edn, JSON) strings.
|
||||
|
||||
Options:
|
||||
`:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
|
||||
`:socket-opts` - {:keys [host port ssl? connect-timeout-msecs]}
|
||||
`:host` - Destination TCP socket hostname string
|
||||
`:port` - Destination TCP socket port int
|
||||
`:ssl?` - Use SSL/TLS (default false)
|
||||
`:connect-timeout-msecs` - Connection timeout (default 3000 msecs)
|
||||
|
||||
`:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
|
||||
|
||||
Limitations:
|
||||
- Failed writes will be retried only once.
|
||||
- Writes lock on a single underlying socket, so IO won't benefit from adding
|
||||
|
|
@ -51,21 +50,22 @@
|
|||
(defn handler:udp-socket
|
||||
"Experimental, subject to change. Feedback welcome!
|
||||
|
||||
Returns a (fn handler [signal]) that:
|
||||
Returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Sends the signal as a string to specified UDP socket.
|
||||
|
||||
Can output signals as human or machine-readable (edn, JSON) strings.
|
||||
|
||||
Options:
|
||||
`:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
|
||||
`:socket-opts` - {:keys [host port max-packet-bytes]}
|
||||
`:host` - Destination UDP socket hostname string
|
||||
`:port` - Destination UDP socket port int
|
||||
`:max-packet-bytes` - Max packet size (in bytes) before truncating output (default 512)
|
||||
|
||||
`:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
|
||||
`:truncation-warning-fn` - Optional (fn [{:keys [max actual signal]}]) to call whenever
|
||||
output is truncated. Should be appropriately rate-limited!
|
||||
`:truncation-warning-fn`
|
||||
Optional (fn [{:keys [max actual signal]}]) to call whenever output is truncated.
|
||||
Should be appropriately rate-limited!
|
||||
|
||||
Limitations:
|
||||
- Due to UDP limitations, truncates output to `max-packet-bytes`!
|
||||
|
|
|
|||
|
|
@ -107,14 +107,14 @@
|
|||
(get signal :error)
|
||||
(enc/identical-kw? (get signal :kind) :error)
|
||||
(case (get signal :level) (:error :fatal) true false)
|
||||
(get signal :error?) ; User kv
|
||||
(get signal :error?) ; Provided kv
|
||||
))))
|
||||
|
||||
(comment (error-signal? {:level :fatal}))
|
||||
|
||||
(defn ^:no-doc remove-signal-kvs
|
||||
"Private, don't use.
|
||||
Returns given signal without user-level kvs or `:kvs` key."
|
||||
Returns given signal without app-level kvs or `:kvs` key."
|
||||
[signal]
|
||||
(if-let [kvs (get signal :kvs)]
|
||||
(reduce-kv (fn [m k _v] (dissoc m k)) (dissoc signal :kvs) kvs)
|
||||
|
|
@ -564,7 +564,7 @@
|
|||
|
||||
Options:
|
||||
`:pr-fn` - ∈ #{<unary-fn> :edn (default) :json (Cljs only)}
|
||||
`:incl-kvs?` - Include signal's user-level kvs? (default false)
|
||||
`:incl-kvs?` - Include signal's app-level kvs? (default false)
|
||||
`:incl-nils?` - Include signal's keys with nil values? (default false)
|
||||
`:incl-newline?` - Include terminating system newline? (default true)
|
||||
`:incl-keys` - Subset of signal keys to retain from those otherwise
|
||||
|
|
@ -697,7 +697,7 @@
|
|||
((format-signal-fn)
|
||||
(tel/with-signal
|
||||
(tel/event! ::ev-id
|
||||
{:user-k1 #{:a :b :c}
|
||||
{:my-k1 #{:a :b :c}
|
||||
:msg "hi"
|
||||
:data {:a :A}
|
||||
;; :error (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"}))
|
||||
|
|
|
|||
|
|
@ -164,15 +164,16 @@ Both have several options, see their docstrings (links above) for details.
|
|||
|
||||
A signal will be provided to a handler iff ALL of the following are true:
|
||||
|
||||
1. Signal **creation** is allowed by **compile-time** "signal filters"
|
||||
2. Signal **creation** is allowed by **runtime** "signal filters"
|
||||
3. Signal **handling** is allowed by **runtime** "handler filters"
|
||||
4. Signal **middleware** does not suppress the signal (return nil)
|
||||
5. Handler **middleware** does not suppress the signal (return nil)
|
||||
|
||||
All filters (1-3) may depend on (in order):
|
||||
|
||||
Sample rate → namespace → kind → id → level → when form/fn → rate limit
|
||||
- 1. Signal **creation** is allowed by **signal filters**:
|
||||
- a. Compile time: sample rate, kind, ns, id, level, when form, rate limit
|
||||
- b. Runtime: sample rate, kind, ns, id, level, when form, rate limit
|
||||
|
||||
- 2. Signal **handling** is allowed by **handler filters**:
|
||||
- a. Compile time: not applicable
|
||||
- b. Runtime: sample rate, kind, ns, id, level, when fn, rate limit
|
||||
|
||||
- 3. **Signal middleware** `(fn [signal]) => ?modified-signal` does not return nil
|
||||
- 4. **Handler middleware** `(fn [signal]) => ?modified-signal` does not return nil
|
||||
|
||||
Quick examples of some basic filtering:
|
||||
|
||||
|
|
@ -199,9 +200,9 @@ Telemere includes extensive internal help docstrings:
|
|||
| Var | Help with |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------ |
|
||||
| [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) | Creating signals |
|
||||
| [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Signal options |
|
||||
| [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Options when creating signals |
|
||||
| [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) | Signal content (map given to middleware/handlers) |
|
||||
| [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) | Signal and handler filters |
|
||||
| [`help:handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handlers) | Signal handlers |
|
||||
| [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) | Signal filtering and transformation |
|
||||
| [`help:handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handlers) | Signal handler management |
|
||||
| [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) | Signal handler dispatch options |
|
||||
| [`help:environmental-config`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:environmental-config) | Config via JVM properties, environment variables, or classpath resources.
|
||||
| [`help:environmental-config`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:environmental-config) | Config via JVM properties, environment variables, or classpath resources. |
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ See below for config by topic-
|
|||
|
||||
A signal will be provided to a handler iff ALL of the following are true:
|
||||
|
||||
1. Signal **creation** is allowed by **compile-time** "signal filters"
|
||||
2. Signal **creation** is allowed by **runtime** "signal filters"
|
||||
3. Signal **handling** is allowed by **runtime** "handler filters"
|
||||
4. Signal **middleware** does not suppress the signal (return nil)
|
||||
5. Handler **middleware** does not suppress the signal (return nil)
|
||||
|
||||
All filters (1-3) may depend on (in order):
|
||||
|
||||
Sample rate → namespace → kind → id → level → when form/fn → rate limit
|
||||
- 1. Signal **creation** is allowed by **signal filters**:
|
||||
- a. Compile time: sample rate, kind, ns, id, level, when form, rate limit
|
||||
- b. Runtime: sample rate, kind, ns, id, level, when form, rate limit
|
||||
|
||||
- 2. Signal **handling** is allowed by **handler filters**:
|
||||
- a. Compile time: not applicable
|
||||
- b. Runtime: sample rate, kind, ns, id, level, when fn, rate limit
|
||||
|
||||
- 3. **Signal middleware** `(fn [signal]) => ?modified-signal` does not return nil
|
||||
- 4. **Handler middleware** `(fn [signal]) => ?modified-signal` does not return nil
|
||||
|
||||
See [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) for more about filtering.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
Signal handlers process created signals to **do something with them** (analyse them, write them to console/file/queue/db, etc.).
|
||||
|
||||
Telemere includes a number of signal handlers out-the-box, and more may be available via the [community](./8-Community#handlers).
|
||||
|
||||
You can also easily [write your own handlers](#writing-handlers) for any output or integration you need.
|
||||
|
||||
# Included handlers
|
||||
|
||||
Telemere includes a number of signal handlers out-the-box.
|
||||
|
||||
[Writing your own handlers](#writing-handlers) is also often straight-forward, and [PRs](https://github.com/taoensso/telemere/pulls) are **very welcome** for additions to Telemere's included handlers, or to Telemere's [community resources](./8-Community).
|
||||
|
||||
It helps to know what people need! You can [vote on](https://www.taoensso.com/roadmap/vote) additional handlers to add, [ping me](https://github.com/taoensso/telemere/issues), or ask on the [`#telemere` Slack channel](https://www.taoensso.com/telemere/slack).
|
||||
|
||||
Current handlers, alphabetically (see linked docstrings below for features and usage):
|
||||
Alphabetically (see linked docstrings below for features and usage):
|
||||
|
||||
| Name | Platform | Output target | Output format |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
|
@ -29,6 +27,8 @@ Planned (upcoming) handlers:
|
|||
| [`handler:carmine`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.carmine#handler:carmine) | Clj | [Redis](https://redis.io/) (via [Carmine](https://www.taoensso.com/carmine)) | [Serialized](https://taoensso.com/nippy) signals |
|
||||
| [`handler:logstash`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.logstash#handler:logstash) | Clj | [Logstash](https://www.elastic.co/logstash) | TODO |
|
||||
|
||||
It helps to know what people need! You can [vote on](https://www.taoensso.com/roadmap/vote) additional handlers to add, [ping me](https://github.com/taoensso/telemere/issues), or ask on the [`#telemere` Slack channel](https://www.taoensso.com/telemere/slack).
|
||||
|
||||
# Configuring handlers
|
||||
|
||||
There's two kinds of config relevant to all signal handlers:
|
||||
|
|
@ -42,7 +42,7 @@ Handler dispatch opts includes dispatch priority (determines order in which hand
|
|||
|
||||
See [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) for full info, and [`default-handler-dispatch-opts`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#default-handler-dispatch-opts) for defaults.
|
||||
|
||||
Note that handler middleware in particular is an often overlooked but powerful feature, allowing you to arbitrarily transform and/or filter every [signal map](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) before it is given to the handler.
|
||||
Note that handler middleware in particular is an often overlooked but powerful feature, allowing you to arbitrarily transform and/or filter every [signal map](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) before it is given to each handler.
|
||||
|
||||
## Handler-specific opts
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ By default it writes formatted strings intended for human consumption:
|
|||
(def my-handler (t/handler:console))
|
||||
|
||||
;; Test handler, remember it's just a (fn [signal])
|
||||
(my-handler my-signal) ; =>
|
||||
(my-handler my-signal) ; %>
|
||||
;; 2024-04-11T10:54:57.202869Z INFO LOG Schrebermann.local examples(56,1) ::my-id - My message
|
||||
;; data: {:x1 :x2}
|
||||
```
|
||||
|
|
@ -81,7 +81,7 @@ To instead writes signals as edn:
|
|||
(t/handler:console
|
||||
{:output-fn (t/pr-signal-fn {:pr-fn :edn})}))
|
||||
|
||||
(my-handler my-signal) ; =>
|
||||
(my-handler my-signal) ; %>
|
||||
;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...}
|
||||
```
|
||||
|
||||
|
|
@ -97,15 +97,18 @@ To instead writes signals as JSON:
|
|||
{:pr-fn
|
||||
#?(:cljs :json
|
||||
:clj jsonista.core/write-value-as-string)})}))
|
||||
|
||||
(my-handler my-signal) ; %>
|
||||
;; {"inst":"2024-04-11T10:54:57.202869Z","msg_":"My message","ns":"examples", ...}
|
||||
```
|
||||
|
||||
Note that when writing JSON with Clojure, you *must* provide an appropriate `pr-fn`. This lets you plug in the JSON serializer of your choice ([jsonista](https://github.com/metosin/jsonista) is my default recommendation).
|
||||
|
||||
### Handler-specific per-signal kvs
|
||||
|
||||
Telemere includes a handy mechanism for including arbitrary user-level data/opts in individual signals for use by custom middleware and/or handlers.
|
||||
Telemere includes a handy mechanism for including arbitrary app-level data/opts in individual signals for use by custom middleware and/or handlers.
|
||||
|
||||
Any *non-standard* (user) keys you include in your signal constructor opts will automatically be included in created signals, e.g.:
|
||||
Any *non-standard* (app-level) keys you include in your signal constructor opts will automatically be included in created signals, e.g.:
|
||||
|
||||
```clojure
|
||||
(t/with-signal
|
||||
|
|
@ -114,20 +117,20 @@ Any *non-standard* (user) keys you include in your signal constructor opts will
|
|||
:my-handler-data "bar"}))
|
||||
|
||||
;; %>
|
||||
;; {;; User kvs included inline (assoc'd to signal root)
|
||||
;; {;; App-level kvs included inline (assoc'd to signal root)
|
||||
;; :my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"
|
||||
;; :kvs ; And also collected together under ":kvs" key
|
||||
;; {:my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"}
|
||||
;; {:my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"}
|
||||
;; ... }
|
||||
```
|
||||
|
||||
These user-level data/opts are typically NOT included by default in handler output, making them a great way to convey data/opts to custom middleware/handlers.
|
||||
These app-level data/opts are typically NOT included by default in handler output, making them a great way to convey data/opts to custom middleware/handlers.
|
||||
|
||||
# Managing handlers
|
||||
|
||||
See [`help:handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-handlers) for info on handler management.
|
||||
See [`help:handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-handlers) for info on signal handler management.
|
||||
|
||||
## Managing handlers on startup
|
||||
|
||||
|
|
@ -135,37 +138,55 @@ Want to add or remove a particular handler when your application starts?
|
|||
|
||||
Just make an appropriate call to [`add-handler!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#add-handler!) or [`remove-handler!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#remove-handler!).
|
||||
|
||||
## Environmental config
|
||||
### Environmental config
|
||||
|
||||
If you want to manage handlers **conditionally** based on **environmental config** (JVM properties, environment variables, or classpath resources) - Telemere provides the highly flexible [`get-env`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-env) util.
|
||||
|
||||
Use this to easily define your own arbitrary cross-platform config, and make whatever conditional handler management decisions you'd like.
|
||||
|
||||
## Stopping handlers
|
||||
|
||||
Telemere supports complex handlers that may use internal state, buffers, etc.
|
||||
|
||||
For this reason, you should **always manually call** [`stop-handlers!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#stop-handlers!) somewhere appropriate to give registered handlers the opportunity to flush buffers, close files, etc.
|
||||
|
||||
The best place to do this is usually near the end of your application's `-main` or shutdown procedure, **AFTER** all other code has completed that could create signals.
|
||||
|
||||
You can use [`call-on-shutdown!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#call-on-shutdown!) to create a JVM shutdown hook.
|
||||
|
||||
Note that `stop-handlers!` will conveniently **block** to finish async handling of any pending signals. The max blocking time can be configured *per-handler* via the `:drain-msecs` [handler dispatch option](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) and defaults to 6 seconds.
|
||||
|
||||
## Handler stats
|
||||
|
||||
By default, Telemere handlers maintain comprehensive internal info about their handling times and outcomes. See [`get-handlers-stats`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats) for more.
|
||||
By default, Telemere handlers maintain **comprehensive internal stats** including handling times and outcome counters.
|
||||
|
||||
This can be **really useful** for debugging handlers, and understanding handler performance and back-pressure behaviour in practice.
|
||||
|
||||
See [`get-handlers-stats`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats) for an output example, etc.
|
||||
|
||||
# Writing handlers
|
||||
|
||||
Writing your own signal handlers for Telemere is straightforward, and a reasonable choice if you prefer customizing behaviour that way, or want to write signals to a DB/format/service for which a ready-made handler isn't available.
|
||||
|
||||
Remember that signals are just plain Clojure/Script [maps](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content), and handlers just plain Clojure/Script functions that do something with those maps.
|
||||
|
||||
Here's a simple Telemere handler:
|
||||
- Signals are just plain Clojure/Script [maps](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content).
|
||||
- Handlers just plain Clojure/Script fns of 2 arities:
|
||||
|
||||
```clojure
|
||||
(fn my-handler [signal] (println signal))
|
||||
(defn my-basic-handler
|
||||
([signal] (println signal)) ; Arity-1 called when handling a signal
|
||||
([]) ; Arity-0 called when stopping the handler
|
||||
)
|
||||
```
|
||||
|
||||
For more complex cases, or for handlers that you want to make available for use by other folks, here's the general template that Telemere uses for all its included handlers:
|
||||
If you're making a customizable handler for use by others, it's often handy to define a handler **constructor**:
|
||||
|
||||
```clojure
|
||||
(defn handler:my-handler ; Note naming convention
|
||||
(defn handler:my-fancy-handler ; Note constructor naming convention
|
||||
"Needs `some-lib`, Ref. <https://github.com/example/some-lib>.
|
||||
|
||||
Returns a (fn handler [signal] that:
|
||||
Returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Does something with the signal.
|
||||
- Does something useful with the signal!
|
||||
|
||||
Options:
|
||||
`:option1` - Option description
|
||||
|
|
@ -175,41 +196,42 @@ For more complex cases, or for handlers that you want to make available for use
|
|||
- Tip 1
|
||||
- Tip 2"
|
||||
|
||||
([] (handler:my-handler nil)) ; Use default opts (when defaults viable)
|
||||
([] (handler:my-fancy-handler nil)) ; Use default opts (iff defaults viable)
|
||||
([{:as constructor-opts}]
|
||||
|
||||
;; Do option validation and other prep here, i.e. try to keep expensive work
|
||||
;; outside handler function when possible.
|
||||
;; Do option validation and other prep here, i.e. try to keep
|
||||
;; expensive work outside handler function when possible!
|
||||
|
||||
(let [handler-fn
|
||||
(fn a-handler:my-handler ; Note naming convention
|
||||
(let [handler-fn ; Fn of exactly 2 arities
|
||||
(fn a-handler:my-fancy-handler ; Note fn naming convention
|
||||
|
||||
;; Main arity, called by Telemere when handler should process given signal
|
||||
([signal]
|
||||
;; Do something with given signal (write to console/file/queue/db, etc.).
|
||||
;; Return value is ignored.
|
||||
([signal] ; Arity-1 called when handling a signal
|
||||
;; Do something useful with the given signal (write to
|
||||
;; console/file/queue/db, etc.). Return value is ignored.
|
||||
)
|
||||
|
||||
;; Optional stop arity for handlers that need to close/release resources or
|
||||
;; otherwise finalize themselves during system shutdown, etc. Called by
|
||||
;; Telemere when appropriate, but ONLY IF the handler's dispatch options
|
||||
;; include a truthy `:needs-stopping?` value (false by default).
|
||||
([]
|
||||
;; Close/release resources, etc.
|
||||
))
|
||||
([] ; Arity-0 called when stopping the handler
|
||||
;; Flush buffers, close files, etc. May just noop.
|
||||
;; Return value is ignored.
|
||||
))]
|
||||
|
||||
;; (Advanced) optional default handler dispatch opts,
|
||||
;; see `add-handler!` for full list of possible opts
|
||||
default-dispatch-opts
|
||||
{:min-level :info
|
||||
:rate-limit [[1 (enc/msecs :min 1)]]}]
|
||||
;; (Advanced, optional) You can use metadata to provide default
|
||||
;; handler dispatch options (see `help:handler-dispatch-options`)
|
||||
|
||||
(with-meta handler-fn default-dispatch-opts))))
|
||||
(with-meta handler-fn
|
||||
{:dispatch-opts
|
||||
{:min-level :info
|
||||
:rate-limit
|
||||
[[1 1000] ; Max 1 signal per second
|
||||
[10 60000] ; Max 10 signals per minute
|
||||
]}}))))
|
||||
```
|
||||
|
||||
- See [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) for signal map content.
|
||||
- See [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) for dispatch options.
|
||||
- See the [utils namespace](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils) for tools useful for customizing and writing signal handlers.
|
||||
- [PRs](https://github.com/taoensso/telemere/pulls) are **very welcome** for additions to Telemere's included handlers, or to Telemere's [community resources](./8-Community)
|
||||
- Feel free to [ping me](https://github.com/taoensso/telemere/issues) for assistance, or ask on the [`#telemere` Slack channel](https://www.taoensso.com/telemere/slack).
|
||||
- [PRs](https://github.com/taoensso/telemere/pulls) are **very welcome** for additions to Telemere's included handlers, or to Telemere's [community resources](./8-Community)!
|
||||
|
||||
# Example output
|
||||
|
||||
|
|
@ -219,7 +241,7 @@ For more complex cases, or for handlers that you want to make available for use
|
|||
|
||||
## Clj console handler
|
||||
|
||||
String output:
|
||||
[API](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | string output:
|
||||
|
||||
```
|
||||
2024-04-11T10:54:57.202869Z INFO LOG Schrebermann.local examples(56,1) ::my-id - My message
|
||||
|
|
@ -228,18 +250,18 @@ String output:
|
|||
|
||||
## Cljs console handler
|
||||
|
||||
Chrome console:
|
||||
[API](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | Chrome console:
|
||||
|
||||
<img src="https://raw.githubusercontent.com/taoensso/telemere/master/imgs/handler-output-cljs-console.png" alt="Default ClojureScript console handler output" width="640"/>
|
||||
|
||||
## Cljs raw console handler
|
||||
|
||||
Chrome console, with [cljs-devtools](https://github.com/binaryage/cljs-devtools):
|
||||
[API](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) | Chrome console, with [cljs-devtools](https://github.com/binaryage/cljs-devtools):
|
||||
|
||||
<img src="https://raw.githubusercontent.com/taoensso/telemere/master/imgs/handler-output-cljs-console-raw.png" alt="Raw ClojureScript console handler output" width="640"/>
|
||||
|
||||
## Clj file handler
|
||||
|
||||
MacOS terminal:
|
||||
[API](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:file) | MacOS terminal:
|
||||
|
||||
<img src="https://raw.githubusercontent.com/taoensso/telemere/master/imgs/handler-output-clj-file.png" alt="Default Clojure file handler output" width="640"/>
|
||||
<img src="https://raw.githubusercontent.com/taoensso/telemere/master/imgs/handler-output-clj-file.png" alt="Default Clojure file handler output" width="640"/>
|
||||
|
|
@ -4,9 +4,9 @@ While [Timbre](https://taoensso.com/timbre) will **continue to be maintained and
|
|||
|
||||
Telemere's functionality is a **superset of Timbre**, and offers *many* improvements including:
|
||||
|
||||
- Significantly better performance
|
||||
- A cleaner and more flexible API
|
||||
- Improved performance
|
||||
- Better support for structured logging
|
||||
- A cleaner and more flexible API
|
||||
- Much better documentation
|
||||
- Better built-in handlers
|
||||
- Easier configuration in many cases
|
||||
|
|
@ -20,7 +20,7 @@ Migrating from Timbre to Telemere should be straightforward **unless you depend
|
|||
|
||||
Where Timbre uses the term "appender", Telemere uses the more general "handler". Functionally they're the same thing.
|
||||
|
||||
Check which **Timbre appenders** you use, and whether a similar handler is [currently included](./4-Handlers#included-handlers) with Telemere or available via the [community](./8-Community).
|
||||
Check which **Timbre appenders** you use, and whether a similar handler is [currently included](./4-Handlers#included-handlers) with Telemere or available via the [community](./8-Community#handlers).
|
||||
|
||||
If not, you may need to [write something yourself](./4-Handlers#writing-handlers).
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,20 @@ They're focused on complementary things. When both are in use:
|
|||
- Tufte can be used for detailed performance measurement, and
|
||||
- Telemere can be used for conveying (aggregate) performance information as part of your system's general observability signals.
|
||||
|
||||
# Does Telemere work with GraalVM?
|
||||
|
||||
> [GraalVM](https://en.wikipedia.org/wiki/GraalVM) is a JDK alternative with ahead-of-time compilation for faster app initialization and improved runtime performance, etc.
|
||||
|
||||
Yes, this shouldn't be a problem.
|
||||
|
||||
# Does Telemere work with Babashka?
|
||||
|
||||
> [Babashka](https://github.com/babashka/babashka) is a native Clojure interpreter for scripting with fast startup.
|
||||
|
||||
Not currently, though support should be possible with a little work. The current bottleneck is a dependency on [Encore](https://github.com/taoensso/encore), though that could actually be removed (also offering benefits re: library size).
|
||||
|
||||
If there's interest in this, please [upvote](https://github.com/taoensso/roadmap/issues/22) on my open source roadmap.
|
||||
|
||||
# Why no format-style messages?
|
||||
|
||||
Telemere's message API can do everything that traditional print *or* format style message builders can do but **much more flexibly** - and with pure Clojure/Script (so no arcane pattern syntax).
|
||||
|
|
@ -103,10 +117,48 @@ See section [9-Maintainers](./9-Maintainers).
|
|||
|
||||
# How does Telemere compare to Mulog?
|
||||
|
||||
> [Mulog](https://github.com/BrunoBonacci/mulog) is an excellent "micro-logging library" for Clojure that shares many of the same capabilities and objectives as Telemere!
|
||||
> [Mulog](https://github.com/BrunoBonacci/mulog) is an excellent "micro-logging library" for Clojure that shares many of the same capabilities and objectives as Telemere.
|
||||
|
||||
TODO - will add a comparison here before Telemere's final release.
|
||||
Some **similarities** between Telemere and Mulog:
|
||||
|
||||
- Both emphasize **structured data** rather than string messages
|
||||
- Both offer **tracing** to understand (nested) program flow
|
||||
- Both offer a (nested) **context** mechanism for arb application state
|
||||
- Both are **fast** and offer **async handling**
|
||||
- Both offer a variety of **handlers** and are designed for ease of use
|
||||
|
||||
Some particular **strengths of Telemere**:
|
||||
|
||||
- Both **Clj and Cljs support** (Mulog is Clj only)
|
||||
- Rich **filtering capabilities** (see [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters)) incl. compile-time elision
|
||||
- Rich **dispatch control** (see [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options))
|
||||
- Rich **environmental config** (see [`help:environmental-config`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:environmental-config)) for all platforms
|
||||
- Detailed **handler stats** (see [`get-handlers-stats`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats))
|
||||
- Extensive [in-IDE documentation](./1-Getting-started#internal-help)
|
||||
- Single **unified API** for all telemetry and traditional logging needs (see [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators))
|
||||
- Lazy `:let`, `:data`, `:msg`, `:do` - evaluated only **after filtering**
|
||||
|
||||
Some particular **strengths of Mulog** that I'm aware of:
|
||||
|
||||
- More **established/mature**
|
||||
- Wider **range of handlers** (incl. Kafka, Kinesis, Prometheus, Zipkin, etc.)
|
||||
- More **community resources** (videos, guides, users, etc.)
|
||||
- **Smaller code** base (Telemere currently depends on [Encore](https://github.com/taoensso/encore))
|
||||
- There may be others!
|
||||
|
||||
**My subjective thoughts**:
|
||||
|
||||
Mulog is an awesome, well-designed library with quality documentation and a solid API. It's **absolutely worth checking out** - you may well prefer it to Telemere!
|
||||
|
||||
The two libraries have many shared capabilities and objectives.
|
||||
|
||||
Ultimately I wrote Telemere because:
|
||||
|
||||
1. I have some particular needs, including very complex and large-scale applications that benefit from the kind of flexibility that Telemere offers re: filtering, dispatch, environmental config, lazy (post-filter) evaluation, etc.
|
||||
2. I have some particular tastes re: my ideal API.
|
||||
3. I wanted something that integrated particularly well with [Tufte](https://taoensso.com/tufte) and could share an identical API for filtering, handlers, etc.
|
||||
4. I wanted a modern replacement for [Timbre](https://www.taoensso.com/timbre) users that offered a superset of its functionality and easy migration path.
|
||||
|
||||
# Other questions?
|
||||
|
||||
Please [open a Github issue](https://github.com/taoensso/telemere/issues) or ping on Telemere's [Slack channel](https://www.taoensso.com/telemere/slack). I'll regularly update the FAQ to add common questions.
|
||||
Please [open a Github issue](https://github.com/taoensso/telemere/issues) or ping on Telemere's [Slack channel](https://www.taoensso.com/telemere/slack). I'll regularly update the FAQ to add common questions. - [Peter](https://www.taoensso.com)
|
||||
|
|
@ -108,7 +108,7 @@ Consider the [differences](https://www.youtube.com/watch?v=oyLBGkS5ICk) between
|
|||
|
||||
Telemere doesn't couple the presence of an error value to signal level. This can be handy, but means that you need to be clear on what constitutes an "error signal" for your use case. See also the [`error-signal?`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#error-signal) util.
|
||||
|
||||
- Signals may contain arbitrary user-level keys.
|
||||
- Signals may contain arbitrary app-level keys.
|
||||
|
||||
Any non-standard [options](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) you give to a signal creator call will be added to the signal it creates:
|
||||
|
||||
|
|
@ -117,9 +117,9 @@ Consider the [differences](https://www.youtube.com/watch?v=oyLBGkS5ICk) between
|
|||
;; => {:my-key "foo", :kvs {:my-key "foo", ...}, ...}
|
||||
```
|
||||
|
||||
Note that all user kvs will *also* be available *together* under the signal's `:kvs` key.
|
||||
Note that all app-level kvs will *also* be available *together* under the signal's `:kvs` key.
|
||||
|
||||
User kvs are typically *not* included in handler output, so are a great way of providing custom data/opts for use (only) by custom middleware or handlers.
|
||||
App-level kvs are typically *not* included in handler output, so are a great way of providing custom data/opts for use (only) by custom middleware or handlers.
|
||||
|
||||
- Signal `kind` can be useful in advanced cases.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,25 @@
|
|||
My plan is for Telemere to offer a **stable core of limited scope**.
|
||||
My plan is for Telemere to offer a **stable core of limited scope**, and leave it to the **community** to author additional stuff like handlers, middleware, and utils.
|
||||
|
||||
If there's demand, additional stuff can then be authored by Telemere's *community*.
|
||||
**PRs very welcome** to add links to this page!
|
||||
|
||||
**PRs very welcome** to add links to this page for:
|
||||
If you spot issues with any linked resources, please **contact the relevant authors** to let them know! Thank you! 🙏 - [Peter](https://www.taoensso.com)
|
||||
|
||||
- Handler libraries or examples (see [Writing handlers](./4-Handlers#writing-handlers))
|
||||
- Handler utils (formatters, etc.)
|
||||
- Middleware
|
||||
- Tutorials / demos / etc.
|
||||
- Anything else relevant :-)
|
||||
# Handlers
|
||||
|
||||
If you spot issues with any linked resources, please **contact the relevant authors** to let them know! Thank you! 🙏
|
||||
Includes libraries or examples for handlers (see [Writing handlers](./4-Handlers#writing-handlers)), middleware, handler utils (e.g. formatters), etc.:
|
||||
|
||||
| Contributor | Link | Description |
|
||||
| :--------------------------------------------- | :---------------------------------------------------------------- | :------------------------------------------------------------ |
|
||||
| [@ptaoussanis](https://github.com/ptaoussanis) | [Official Slack channel](https://www.taoensso.com/telemere/slack) | For questions, support, etc. |
|
||||
| [@ptaoussanis](https://github.com/ptaoussanis) | [GitHub issues](https://github.com/taoensso/telemere/issues) | For questions, support, bug reports, PRs, etc. |
|
||||
| _ | _ | Your link here? [PRs](../wiki#contributions-welcome) welcome! |
|
||||
| [@username](https://github.com/username) | [Project](https://github.com/username/project) | Short description of resource |
|
||||
| Date | Link | Description |
|
||||
| :--- | :--- | :------------------------------------------------------------ |
|
||||
| - | - | Your link here? [PRs](../wiki#contributions-welcome) welcome! |
|
||||
|
||||
# Learning
|
||||
|
||||
Includes videos, tutorials, demo projects, etc.:
|
||||
|
||||
| Date | Link | Description |
|
||||
| :--------- | :---------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| - | - | Your link here? [PRs](../wiki#contributions-welcome) welcome! |
|
||||
| - | [Official Slack channel](https://www.taoensso.com/telemere/slack) | For questions, support, etc. |
|
||||
| - | [GitHub issues](https://github.com/taoensso/telemere/issues) | For questions, support, bug reports, PRs, etc. |
|
||||
| 2024-06-12 | [YouTube](https://www.youtube.com/watch?v=uyApiNg6h7Y) | [Los Angeles Clojure Users Group](https://www.meetup.com/los-angeles-clojure-users-group/) collaborative learning session (107 mins) |
|
||||
| 2024-04-18 | [YouTube](https://www.youtube.com/watch?v=-L9irDG8ysM) | Official Telemere announcement demo (24 mins) |
|
||||
|
|
|
|||
Loading…
Reference in a new issue