telemere/examples.cljc

382 lines
12 KiB
Text
Raw Normal View History

2024-03-13 11:13:27 +00:00
(ns examples
2024-04-11 09:05:20 +00:00
"Basic Telemere usage examples that appear in the Wiki or docstrings."
2025-03-10 21:31:54 +00:00
(:require [taoensso.telemere :as tel]))
2024-03-13 11:13:27 +00:00
2024-08-22 07:02:21 +00:00
(comment
;;;; README "Quick examples"
2024-04-11 09:05:20 +00:00
2025-03-10 21:31:54 +00:00
(require '[taoensso.telemere :as tel])
2024-04-11 09:05:20 +00:00
;; No config needed for typical use cases!!
;; Signals print to console by default for both Clj and Cljs
2024-08-24 08:57:56 +00:00
;; Traditional style logging (data formatted into message string):
(tel/log! {:level :info, :msg (str "User " 1234 " logged in!")})
2024-08-05 10:59:18 +00:00
;; Modern/structured style logging (explicit id and data)
(tel/log! {:level :info, :id :auth/login, :data {:user-id 1234}})
2024-04-11 09:05:20 +00:00
;; Mixed style (explicit id and data, with message string)
(tel/log! {:level :info, :id :auth/login, :data {:user-id 1234}, :msg "User logged in!"})
;; Trace (can interop with OpenTelemetry)
2024-08-24 08:57:56 +00:00
;; Tracks form runtime, return value, and (nested) parent tree
(tel/trace! {:id ::my-id :data {...}}
2024-08-22 07:02:21 +00:00
(do-some-work))
2024-04-11 09:05:20 +00:00
2024-08-24 08:57:56 +00:00
;; Check resulting signal content for debug/tests
(tel/with-signal (tel/log! {...})) ; => {:keys [ns level id data msg_ ...]}
2024-04-11 09:05:20 +00:00
2024-08-22 07:02:21 +00:00
;; Getting fancy (all costs are conditional!)
2025-03-10 21:31:54 +00:00
(tel/log!
{:level :debug
:sample 0.75 ; 75% sampling (noop 25% of the time)
:when (my-conditional)
:limit {"1 per sec" [1 1000]
"5 per min" [5 60000]} ; Rate limit
:limit-by my-user-ip-address ; Rate limit scope
2024-04-11 09:05:20 +00:00
2024-08-22 07:02:21 +00:00
:do (inc-my-metric!)
:let
[diagnostics (my-expensive-diagnostics)
formatted (my-expensive-format diagnostics)]
2024-08-05 10:59:18 +00:00
2024-08-22 07:02:21 +00:00
:data
{:diagnostics diagnostics
:formatted formatted
:local-state *my-dynamic-context*}}
2024-08-05 10:59:18 +00:00
2024-08-22 07:02:21 +00:00
;; Message string or vector to join as string
["Something interesting happened!" formatted])
)
2024-08-05 10:59:18 +00:00
2024-08-22 07:02:21 +00:00
;; Set minimum level
2025-03-10 21:31:54 +00:00
(tel/set-min-level! :warn) ; For all signals
(tel/set-min-level! :log :debug) ; For `log!` signals specifically
2024-04-11 09:05:20 +00:00
;; Set id and namespace filters
2025-03-10 21:31:54 +00:00
(tel/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})
(tel/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
2024-04-11 09:05:20 +00:00
;; SLF4J signals will have their `:ns` key set to the logger's name
;; (typically a source class)
(tel/set-ns-filter! {:disallow "com.noisy.java.package.*"})
;; Set minimum level for `log!` signals for particular ns pattern
(tel/set-min-level! :log "taoensso.sente.*" :warn)
2024-04-11 09:05:20 +00:00
;; Use transforms (xfns) to filter and/or arbitrarily modify signals
;; by signal data/content/etc.
2024-08-05 10:59:18 +00:00
2025-03-10 21:31:54 +00:00
(tel/set-xfn!
2024-08-05 10:59:18 +00:00
(fn [signal]
2024-08-22 07:02:21 +00:00
(if (-> signal :data :skip-me?)
nil ; Filter signal (don't handle)
(assoc signal :transformed? true))))
2024-08-05 10:59:18 +00:00
(tel/with-signal (tel/log! {... :data {:skip-me? true}})) ; => nil
(tel/with-signal (tel/log! {... :data {:skip-me? false}})) ; => {...}
2024-04-11 09:05:20 +00:00
2025-03-10 21:31:54 +00:00
;; See `tel/help:filters` docstring for more filtering options
2024-08-22 07:02:21 +00:00
2024-09-25 12:07:56 +00:00
;; Add your own signal handler
2025-03-10 21:31:54 +00:00
(tel/add-handler! :my-handler
2024-09-25 12:07:56 +00:00
(fn
([signal] (println signal))
2024-10-02 08:42:22 +00:00
([] (println "Handler has shut down"))))
2024-09-25 12:07:56 +00:00
;; Use `add-handler!` to set handler-level filtering and back-pressure
2025-03-10 21:31:54 +00:00
(tel/add-handler! :my-handler
2024-09-25 12:07:56 +00:00
(fn
([signal] (println signal))
2024-10-02 08:42:22 +00:00
([] (println "Handler has shut down")))
2024-08-22 07:02:21 +00:00
{:async {:mode :dropping, :buffer-size 1024, :n-threads 1}
:priority 100
:sample 0.5
:min-level :info
:ns-filter {:disallow "taoensso.*"}
:limit {"1 per sec" [1 1000]}
2025-03-10 21:31:54 +00:00
;; See `tel/help:handler-dispatch-options` for more
2024-08-22 07:02:21 +00:00
})
2024-09-25 12:07:56 +00:00
;; See current handlers
2025-03-10 21:31:54 +00:00
(tel/get-handlers) ; => {<handler-id> {:keys [handler-fn handler-stats_ dispatch-opts]}}
2024-09-25 12:07:56 +00:00
2024-10-20 18:49:37 +00:00
;; Add console handler to print signals as human-readable text
2025-03-10 21:31:54 +00:00
(tel/add-handler! :my-handler
(tel/handler:console
{:output-fn (tel/format-signal-fn {})}))
2024-08-24 08:57:56 +00:00
2024-10-20 18:49:37 +00:00
;; Add console handler to print signals as edn
2025-03-10 21:31:54 +00:00
(tel/add-handler! :my-handler
(tel/handler:console
{:output-fn (tel/pr-signal-fn {:pr-fn :edn})}))
2024-08-24 08:57:56 +00:00
2024-10-20 18:49:37 +00:00
;; Add console handler to print signals as JSON
2024-08-24 08:57:56 +00:00
;; Ref. <https://github.com/metosin/jsonista> (or any alt JSON lib)
#?(:clj (require '[jsonista.core :as jsonista]))
2025-03-10 21:31:54 +00:00
(tel/add-handler! :my-handler
(tel/handler:console
2024-08-24 08:57:56 +00:00
{:output-fn
#?(:cljs :json ; Use js/JSON.stringify
2024-09-25 12:07:56 +00:00
:clj jsonista/write-value-as-string)}))
2024-08-24 08:57:56 +00:00
2024-08-22 07:02:21 +00:00
;;;; Docstring examples
2024-03-13 11:13:27 +00:00
2025-03-10 21:31:54 +00:00
(tel/with-signal (tel/event! ::my-id))
(tel/with-signal (tel/event! ::my-id :warn))
(tel/with-signal
(tel/event! ::my-id
2024-03-13 11:13:27 +00:00
{:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x}
:msg ["My msg:" x]}))
2025-03-10 21:31:54 +00:00
(tel/with-signal (tel/log! "My msg"))
(tel/with-signal (tel/log! :warn "My msg"))
(tel/with-signal
(tel/log!
2024-03-13 11:13:27 +00:00
{:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x}}
["My msg:" x]))
2025-03-10 21:31:54 +00:00
(tel/with-signal (throw (tel/error! (ex-info "MyEx" {}))))
(tel/with-signal (throw (tel/error! ::my-id (ex-info "MyEx" {}))))
(tel/with-signal
2024-03-13 11:13:27 +00:00
(throw
2025-03-10 21:31:54 +00:00
(tel/error!
2024-03-13 11:13:27 +00:00
{:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x}
:msg ["My msg:" x]}
(ex-info "MyEx" {}))))
2025-03-10 21:31:54 +00:00
(tel/with-signal (tel/trace! (+ 1 2)))
(tel/with-signal (tel/trace! ::my-id (+ 1 2)))
(tel/with-signal
(tel/trace!
2024-03-13 11:13:27 +00:00
{:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x}}
(+ 1 2)))
2025-03-10 21:31:54 +00:00
(tel/with-signal (tel/spy! (+ 1 2)))
(tel/with-signal (tel/spy! :debug (+ 1 2)))
(tel/with-signal
(tel/spy!
2024-03-13 11:13:27 +00:00
{:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x}}
(+ 1 2)))
2025-03-10 21:31:54 +00:00
(tel/with-signal (tel/catch->error! (/ 1 0)))
(tel/with-signal (tel/catch->error! ::my-id (/ 1 0)))
(tel/with-signal
(tel/catch->error!
2024-03-13 11:13:27 +00:00
{:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x}
:msg ["My msg:" x]
:catch-val "Return value when form throws"}
2024-03-13 11:13:27 +00:00
(/ 1 0)))
2024-04-11 09:05:20 +00:00
;;;; Wiki examples
;;; Filter signals
2025-03-10 21:31:54 +00:00
(tel/set-min-level! :info) ; Set global minimum level
(tel/with-signal (tel/event! ::my-id1 :info)) ; => {:keys [inst id ...]}
(tel/with-signal (tel/event! ::my-id1 :debug)) ; => nil (signal not allowed)
2024-04-11 09:05:20 +00:00
2025-03-10 21:31:54 +00:00
(tel/with-min-level :trace ; Override global minimum level
(tel/with-signal (tel/event! ::my-id1 :debug))) ; => {:keys [inst id ...]}
2024-04-11 09:05:20 +00:00
2024-08-07 10:00:22 +00:00
;; Disallow all signals in matching namespaces
2025-03-10 21:31:54 +00:00
(tel/set-ns-filter! {:disallow "some.nosy.namespace.*"})
2024-04-15 07:57:40 +00:00
;;; Configuring handlers
;; Create a test signal
(def my-signal
2025-03-10 21:31:54 +00:00
(tel/with-signal
(tel/log! {:id ::my-id, :data {:x1 :x2}} "My message")))
2024-04-15 07:57:40 +00:00
;; Create console handler with default opts (writes formatted string)
2025-03-10 21:31:54 +00:00
(def my-handler (tel/handler:console {}))
2024-04-15 07:57:40 +00:00
;; Test handler, remember it's just a (fn [signal])
2024-08-05 10:59:18 +00:00
(my-handler my-signal) ; %>
2024-08-22 07:02:21 +00:00
;; 2024-04-11T10:54:57.202869Z INFO LOG MyHost examples(56,1) ::my-id - My message
2024-04-15 07:57:40 +00:00
;; data: {:x1 :x2}
2024-08-05 10:59:18 +00:00
;; Create console handler which writes signals as edn
2024-04-15 07:57:40 +00:00
(def my-handler
2025-03-10 21:31:54 +00:00
(tel/handler:console
{:output-fn (tel/pr-signal-fn {:pr-fn :edn})}))
2024-04-15 07:57:40 +00:00
2024-08-05 10:59:18 +00:00
(my-handler my-signal) ; %>
2024-04-15 07:57:40 +00:00
;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...}
2024-08-05 10:59:18 +00:00
;; Create console handler which writes signals as JSON
2024-08-24 08:57:56 +00:00
;; Ref. <https://github.com/metosin/jsonista> (or any alt JSON lib)
#?(:clj (require '[jsonista.core :as jsonista]))
2024-04-15 07:57:40 +00:00
(def my-handler
2025-03-10 21:31:54 +00:00
(tel/handler:console
{:output-fn
2025-03-10 21:31:54 +00:00
(tel/pr-signal-fn
{:pr-fn
2024-08-24 08:57:56 +00:00
#?(:cljs :json ; Use js/JSON.stringify
:clj jsonista/write-value-as-string)})}))
2024-04-15 07:57:40 +00:00
2024-08-05 10:59:18 +00:00
(my-handler my-signal) ; %>
2024-04-15 07:57:40 +00:00
;; {"inst":"2024-04-11T10:54:57.202869Z","msg_":"My message","ns":"examples", ...}
2024-08-05 10:59:18 +00:00
;; Deregister the default console handler
2025-03-10 21:31:54 +00:00
(tel/remove-handler! :defaultel/console)
2024-08-05 10:59:18 +00:00
;; Register our custom console handler
2025-03-10 21:31:54 +00:00
(tel/add-handler! :my-handler my-handler
2024-08-05 10:59:18 +00:00
;; 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
2025-03-10 21:31:54 +00:00
(tel/call-on-shutdown!
(fn [] (tel/stop-handlers!)))
2024-08-05 10:59:18 +00:00
2025-03-10 21:31:54 +00:00
;; See `tel/help:handlers` docstring for more
2024-08-05 10:59:18 +00:00
2024-04-15 07:57:40 +00:00
;;; Writing handlers
2024-08-05 10:59:18 +00:00
;; Handlers are just fns of 2 arities
(defn my-basic-handler
([]) ; Arity-0 called when stopping the handler
2024-08-26 14:37:40 +00:00
([signal] (println signal)) ; Arity-1 called when handling a signal
2024-08-05 10:59:18 +00:00
)
;; 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
2024-05-05 12:15:09 +00:00
"Needs `some-lib`, Ref. <https://github.com/example/some-lib>.
2024-08-05 10:59:18 +00:00
Returns a signal handler that:
2024-05-05 12:15:09 +00:00
- Takes a Telemere signal (map).
2024-08-05 10:59:18 +00:00
- Does something useful with the signal!
2024-04-15 07:57:40 +00:00
Options:
2024-05-05 12:15:09 +00:00
`:option1` - Option description
`:option2` - Option description
Tips:
- Tip 1
- Tip 2"
2024-04-15 07:57:40 +00:00
2024-08-05 10:59:18 +00:00
([] (handler:my-fancy-handler nil)) ; Use default opts (iff defaults viable)
2024-04-15 07:57:40 +00:00
([{:as constructor-opts}]
2024-08-05 10:59:18 +00:00
;; Do option validation and other prep here, i.e. try to keep
;; expensive work outside handler function when possible!
2024-05-05 12:15:09 +00:00
2025-01-15 09:26:36 +00:00
(let [handler-fn ; Fn of exactly 2 arities (1 and 0)
2024-08-05 10:59:18 +00:00
(fn a-handler:my-fancy-handler ; Note fn naming convention
2024-05-05 12:15:09 +00:00
2024-08-26 14:37:40 +00:00
([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.
2025-01-15 09:26:36 +00:00
)
([] ; Arity-0 called when stopping the handler
;; Flush buffers, close files, etc. May just noop.
;; Return value is ignored.
2024-08-05 10:59:18 +00:00
))]
2024-04-15 07:57:40 +00:00
2024-08-05 10:59:18 +00:00
;; (Advanced, optional) You can use metadata to provide default
;; handler dispatch options (see `help:handler-dispatch-options`)
2024-04-15 07:57:40 +00:00
2024-08-05 10:59:18 +00:00
(with-meta handler-fn
{:dispatch-opts
{:min-level :info
:limit
2024-08-05 10:59:18 +00:00
[[1 1000] ; Max 1 signal per second
[10 60000] ; Max 10 signals per minute
]}}))))
2024-04-18 10:14:35 +00:00
;;; Message building
;; A fixed message (string arg)
2025-03-10 21:31:54 +00:00
(tel/log! "A fixed message") ; %> {:msg "A fixed message"}
2024-04-18 10:14:35 +00:00
;; A joined message (vector arg)
(let [user-arg "Bob"]
2025-03-10 21:31:54 +00:00
(tel/log! ["User" (str "`" user-arg "`") "just logged in!"]))
2024-04-18 10:14:35 +00:00
;; %> {:msg_ "User `Bob` just logged in!` ...}
;; With arg prep
(let [user-arg "Bob"
usd-balance-str "22.4821"]
2025-03-10 21:31:54 +00:00
(tel/log!
2024-04-18 10:14:35 +00:00
{:let
[username (clojure.string/upper-case user-arg)
usd-balance (parse-double usd-balance-str)]
:data
{:username username
:usd-balance usd-balance}}
["User" username "has balance:" (str "$" (Math/round usd-balance))]))
;; %> {:msg "User BOB has balance: $22" ...}
2025-03-10 21:31:54 +00:00
(tel/log! (str "This message " "was built " "by `str`"))
2024-04-18 10:14:35 +00:00
;; %> {:msg "This message was built by `str`"}
2025-03-10 21:31:54 +00:00
(tel/log! (enc/format "This message was built by `%s`" "format"))
2024-04-18 10:14:35 +00:00
;; %> {:msg "This message was built by `format`"}
2024-05-03 06:53:16 +00:00
2024-08-05 10:59:18 +00:00
;;; App-level kvs
2024-05-03 06:53:16 +00:00
2025-03-10 21:31:54 +00:00
(tel/with-signal
(tel/event! ::my-id
{:my-data-for-xfn "foo"
:my-data-for-handler "bar"}))
2024-05-03 06:53:16 +00:00
;; %>
2024-08-05 10:59:18 +00:00
;; {;; App-level kvs included inline (assoc'd to signal root)
;; :my-data-for-xfn "foo"
;; :my-data-for-handler "bar"
2024-05-03 06:53:16 +00:00
;; :kvs ; And also collected together under ":kvs" key
;; {:my-data-for-xfn "foo"
;; :my-data-for-handler "bar"}
2024-05-03 06:53:16 +00:00
;; ... }
2024-08-22 07:02:21 +00:00
;;;; Misc extra examples
2025-03-10 21:31:54 +00:00
(tel/log! {:id ::my-id, :data {:x1 :x2}} ["My 2-part" "message"]) ; %>
2024-08-22 07:02:21 +00:00
;; 2024-04-11T10:54:57.202869Z INFO LOG MyHost examples(56,1) ::my-id - My 2-part message
;; data: {:x1 :x2}
;; `:let` bindings are available to `:data` and message, but only paid
;; for when allowed by minimum level and other filtering criteria
2025-03-10 21:31:54 +00:00
(tel/log!
2024-08-22 07:02:21 +00:00
{:level :info
:let [expensive (reduce * (range 1 12))] ; 12 factorial
:data {:my-metric expensive}}
["Message with metric:" expensive])
;; With sampling 50% and 1/sec rate limiting
2025-03-10 21:31:54 +00:00
(tel/log!
{:sample 0.5
:limit {"1 per sec" [1 1000]}}
2024-08-22 07:02:21 +00:00
"This signal will be sampled and rate limited")
;; Several signal creators are available for convenience.
;; All offer the same options, but each has an API optimized
;; for a particular use case:
2025-03-10 21:31:54 +00:00
(tel/log! {:level :info, :id ::my-id} "Hi!") ; [msg] or [level-or-opts msg]
(tel/event! ::my-id {:level :info, :msg "Hi!"}) ; [id] or [id level-or-opts]
(tel/signal! {:level :info, :id ::my-id, :msg "Hi!"}) ; [opts]
2024-08-22 07:02:21 +00:00
)