mirror of
https://github.com/taoensso/telemere.git
synced 2025-12-16 17:41:12 +00:00
[doc] Update docs and examples
This commit is contained in:
parent
84957c6d0a
commit
ddc9480d20
10 changed files with 283 additions and 206 deletions
197
README.md
197
README.md
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
It helps enable Clojure/Script systems that are **observable**, **robust**, and **debuggable** - and it represents the refinement and culmination of ideas brewing over 12+ years in [Timbre](https://www.taoensso.com/timbre), [Tufte](https://www.taoensso.com/tufte), [Truss](https://www.taoensso.com/truss), etc.
|
||||
|
||||
> [Terminology] *Telemetry* derives from the Greek *tele* (remote) and *metron* (measure). It refers to the collection of *in situ* (in position) data, for transmission to other systems for monitoring/analysis. *Logs* are the most common form of software telemetry. So think of telemetry as the *superset of logging-like activities* that help monitor and understand (software) systems.
|
||||
See [here](../../wiki/1-Getting-started) for **full introduction**.
|
||||
|
||||
## Latest release/s
|
||||
|
||||
|
|
@ -20,27 +20,75 @@ It helps enable Clojure/Script systems that are **observable**, **robust**, and
|
|||
|
||||
See [here][GitHub releases] for earlier releases.
|
||||
|
||||
## Quick examples
|
||||
|
||||
```clojure
|
||||
(require '[taoensso.telemere :as t])
|
||||
|
||||
;; Without structured data
|
||||
(t/log! :info "Hello world!") ; %> Basic log signal (has message)
|
||||
(t/event! ::my-id :debug) ; %> Basic event signal (just id)
|
||||
|
||||
;; With structured data
|
||||
(t/log! {:level :info, :data {...}} "Hello again!")
|
||||
(t/event! ::my-id {:level :debug, :data {...}})
|
||||
|
||||
;; Trace (can interop with OpenTelemetry)
|
||||
(t/trace! {:id ::my-id :data {...}}
|
||||
(do-some-work))
|
||||
|
||||
;; Check signal content for debug/tests
|
||||
(t/with-signal (t/event! ::my-id)) ; => {:keys [ns level id data msg_ ...]}
|
||||
|
||||
;; Transform signals
|
||||
(t/set-middleware! (fn [signal] (assoc signal :my-key "my-val")))
|
||||
|
||||
;; Filter signals by returning nil
|
||||
(t/set-middleware! (fn [signal] (when-not (-> signal :data :skip-me?) signal)))
|
||||
|
||||
;; Getting fancy (all costs are conditional!)
|
||||
(t/log!
|
||||
{:level :debug
|
||||
:sample-rate (my-dynamic-sample-rate)
|
||||
:when (my-conditional)
|
||||
:rate-limit {"1 per sec" [1 1000]
|
||||
"5 per min" [5 60000]}
|
||||
|
||||
:do (inc-my-metric!)
|
||||
:let
|
||||
[diagnostics (my-expensive-diagnostics)
|
||||
formatted (my-expensive-format diagnostics)]
|
||||
|
||||
:data
|
||||
{:diagnostics diagnostics
|
||||
:formatted formatted
|
||||
:local-state *my-dynamic-context*}}
|
||||
|
||||
;; Message string or vector to join as string
|
||||
["Something interesting happened!" formatted])
|
||||
```
|
||||
|
||||
## Why Telemere?
|
||||
|
||||
#### Ergonomics
|
||||
### Ergonomics
|
||||
|
||||
- Elegant, lightweight API that's **easy to use**, **easy to configure**, and **deeply flexible**.
|
||||
- **Sensible defaults** to make getting started **fast and easy**.
|
||||
- Extensive **beginner-oriented** [documentation][GitHub wiki], [docstrings](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso), and error messages.
|
||||
|
||||
#### Interop
|
||||
### Interop
|
||||
|
||||
- 1st-class **out-the-box interop** with [SLF4J v2](../../wiki/3-Config#java-logging), [tools.logging](../../wiki/3-Config#toolslogging), [OpenTelemetry](../../wiki/3-Config#opentelemetry), and [Tufte](../../wiki/3-Config#tufte).
|
||||
- Included [shim](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.timbre) for easy/gradual [migration from Timbre](../../wiki/5-Migrating).
|
||||
- Extensive set of [handlers](../../wiki/4-Handlers#included-handlers) included out-the-box.
|
||||
|
||||
#### Scaling
|
||||
### Scaling
|
||||
|
||||
- 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/CURRENT/api/taoensso.telemere#get-handlers-stats) for debugging performance and other issues at scale.
|
||||
|
||||
#### Flexibility
|
||||
### Flexibility
|
||||
|
||||
- Config via plain **Clojure vals and fns** for easy customization, composition, and REPL debugging.
|
||||
- Unmatched [environmental config](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:environmental-config) support: JVM properties, environment variables, or classpath resources. Per platform, or cross-platform.
|
||||
|
|
@ -48,10 +96,24 @@ 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.
|
||||
|
||||
#### Comparisons
|
||||
## Comparisons
|
||||
|
||||
- Telemere [compared](../../wiki/5-Migrating#from-timbre) to [Timbre](https://www.taoensso.com/timbre)
|
||||
- Telemere [compared](../../wiki/6-FAQ#how-does-telemere-compare-to-mulog) to [Mulog](https://github.com/BrunoBonacci/mulog)
|
||||
- Telemere [compared](../../wiki/5-Migrating#from-timbre) to [Timbre](https://www.taoensso.com/timbre) (Telemere's predecessor)
|
||||
- Telemere [compared](../../wiki/6-FAQ#how-does-telemere-compare-to-mulog) to [Mulog](https://github.com/BrunoBonacci/mulog) (Structured micro-logging library)
|
||||
|
||||
## Next-gen observability
|
||||
|
||||
A key hurdle in building **observable systems** is that it's often inconvenient and costly to get out the kind of **detailed info** that we need when debugging.
|
||||
|
||||
Telemere's strategy to address this is to:
|
||||
|
||||
1. Provide **lean, low-fuss syntax** to let you conveniently convey program state.
|
||||
2. Use the unique power of **Lisp macros** to let you **dynamically filter costs as you filter signals** (pay only for what you need, when you need it).
|
||||
3. For those signals that *do* pass filtering: move costs from the callsite to a/sync handlers with explicit [threading and back-pressure semantics](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) and [performance monitoring](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats).
|
||||
|
||||
The effect is more than impressive micro-benchmarks. This approach enables a fundamental (qualitative) change in one's approach to observability.
|
||||
|
||||
It enables you to write code that is **information-verbose by default**.
|
||||
|
||||
## Video demo
|
||||
|
||||
|
|
@ -61,89 +123,52 @@ See for intro and basic usage:
|
|||
<img src="https://img.youtube.com/vi/-L9irDG8ysM/maxresdefault.jpg" alt="Telemere demo video" width="480" border="0" />
|
||||
</a>
|
||||
|
||||
## Quick examples
|
||||
## More examples
|
||||
|
||||
```clojure
|
||||
(require '[taoensso.telemere :as t])
|
||||
|
||||
(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 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
|
||||
(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 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)))]
|
||||
:data {:metric1 expensive-metric1}}
|
||||
["Message with metric value:" expensive-metric1])
|
||||
|
||||
;; With sampling 50% and 1/sec rate limiting
|
||||
(t/log!
|
||||
{:sample-rate 0.5
|
||||
:rate-limit {"1 per sec" [1 1000]}}
|
||||
"This signal will be sampled and rate limited")
|
||||
|
||||
;; 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
|
||||
;; Set minimum level
|
||||
(t/set-min-level! :warn) ; For all signals
|
||||
(t/set-min-level! :log :debug) ; For `log!` signals only
|
||||
|
||||
;; Set namespace and id filters
|
||||
(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
|
||||
;; Set minimum level for `event!` signals for particular ns pattern
|
||||
(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.
|
||||
;; Use middleware to:
|
||||
;; - Transform signals
|
||||
;; - Filter signals by arb conditions (incl. data/content)
|
||||
|
||||
(t/set-middleware!
|
||||
(fn [signal]
|
||||
(if (get-in signal [:data :hide-me?])
|
||||
nil ; Suppress signal (don't handle)
|
||||
(if (-> signal :data :skip-me?)
|
||||
nil ; Filter 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}})) ; => {...}
|
||||
(t/with-signal (t/event! ::my-id {:data {:skip-me? true}})) ; => nil
|
||||
(t/with-signal (t/event! ::my-id {:data {:skip-me? false}})) ; => {...}
|
||||
|
||||
;; Signal handlers
|
||||
|
||||
(t/get-handlers) ; => {<handler-id> {:keys [handler-fn handler-stats_ dispatch-opts]}}
|
||||
|
||||
(t/add-handler! :my-console-handler
|
||||
(t/handler:console {}) ; Returns handler fn, has many opts
|
||||
{:async {:mode :dropping, :buffer-size 1024, :n-threads 1}
|
||||
:priority 100
|
||||
:sample-rate 0.5
|
||||
:min-level :info
|
||||
:ns-filter {:disallow "taoensso.*"}
|
||||
:rate-limit {"1 per sec" [1 1000]}
|
||||
;; See `t/help:handler-dispatch-options` for more
|
||||
})
|
||||
```
|
||||
|
||||
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 REPL-ready snippets!
|
||||
|
||||
## API overview
|
||||
|
||||
|
|
@ -165,20 +190,20 @@ See relevant docstrings (links below) for usage info-
|
|||
|
||||
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) | 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 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. |
|
||||
| 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) | 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 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 |
|
||||
|
||||
### Included handlers
|
||||
|
||||
> See ✅ links for **features and usage**
|
||||
> See 👍 links to **vote on handler** for future addition
|
||||
See ✅ links below for **features and usage**,
|
||||
See 👍 links below to **vote on future handlers**:
|
||||
|
||||
| Target (↓) | Clj | Cljs |
|
||||
| :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: |
|
||||
|
|
@ -234,7 +259,7 @@ Measurements:
|
|||
|
||||
### Performance philosophy
|
||||
|
||||
Telemere is optimized for *real-world* performance. This means **prioritizing flexibility** and realistic usage over synthetic micro benchmarks.
|
||||
Telemere is optimized for *real-world* performance. This means **prioritizing flexibility** and realistic usage over synthetic micro-benchmarks.
|
||||
|
||||
Large applications can produce absolute *heaps* of data, not all equally valuable. Quickly processing infinite streams of unmanageable junk is an anti-pattern. As scale and complexity increase, it becomes more important to **strategically plan** what data to collect, when, in what quantities, and how to manage it.
|
||||
|
||||
|
|
|
|||
150
examples.cljc
150
examples.cljc
|
|
@ -2,86 +2,98 @@
|
|||
"Basic Telemere usage examples that appear in the Wiki or docstrings."
|
||||
(:require [taoensso.telemere :as t]))
|
||||
|
||||
;;;; README "Quick example"
|
||||
(comment
|
||||
|
||||
;;;; README "Quick examples"
|
||||
|
||||
(require '[taoensso.telemere :as t])
|
||||
|
||||
(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}
|
||||
;; Without structured data
|
||||
(t/log! :info "Hello world!") ; %> Basic log signal (has message)
|
||||
(t/event! ::my-id :debug) ; %> Basic event signal (just id)
|
||||
|
||||
(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`")
|
||||
;; With structured data
|
||||
(t/log! {:level :info, :data {}} "Hello again!")
|
||||
(t/event! ::my-id {:level :debug, :data {}})
|
||||
|
||||
;; Easily construct messages from parts
|
||||
(t/log! :info ["Here's a" "joined" "message!"])
|
||||
;; Trace (can interop with OpenTelemetry)
|
||||
(t/trace! {:id ::my-id :data {}}
|
||||
(do-some-work))
|
||||
|
||||
;; Attach an id
|
||||
(t/log! {:level :info, :id ::my-id} "This signal has an id")
|
||||
;; Check signal content for debug/tests
|
||||
(t/with-signal (t/event! ::my-id)) ; => {:keys [ns level id data msg_ ...]}
|
||||
|
||||
;; Attach arb data
|
||||
(t/log! {:level :info, :data {:x :y}} "This signal has structured data")
|
||||
;; Transform signals
|
||||
(t/set-middleware! (fn [signal] (assoc signal :my-key "my-val")))
|
||||
|
||||
;; Capture for debug/testing
|
||||
(t/with-signal (t/log! "This will be captured"))
|
||||
;; => {:keys [location level id data msg_ ...]}
|
||||
;; Filter signals by returning nil
|
||||
(t/set-middleware! (fn [signal] (when-not (-> signal :data :skip-me?) signal)))
|
||||
|
||||
;; `:let` bindings are available to `:data` and message, but only paid
|
||||
;; for when allowed by minimum level and other filtering criteria
|
||||
;; Getting fancy (all costs are conditional!)
|
||||
(t/log!
|
||||
{:level :info
|
||||
:let [expensive-metric1 (last (for [x (range 100), y (range 100)] (* x y)))]
|
||||
:data {:metric1 expensive-metric1}}
|
||||
["Message with metric value:" expensive-metric1])
|
||||
{:level :debug
|
||||
:sample-rate (my-dynamic-sample-rate)
|
||||
:when (my-conditional)
|
||||
:rate-limit {"1 per sec" [1 1000]
|
||||
"5 per min" [5 60000]}
|
||||
|
||||
;; With sampling 50% and 1/sec rate limiting
|
||||
(t/log!
|
||||
{:sample-rate 0.5
|
||||
:rate-limit {"1 per sec" [1 1000]}}
|
||||
"This signal will be sampled and rate limited")
|
||||
:do (inc-my-metric!)
|
||||
:let
|
||||
[diagnostics (my-expensive-diagnostics)
|
||||
formatted (my-expensive-format diagnostics)]
|
||||
|
||||
;; 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:
|
||||
:data
|
||||
{:diagnostics diagnostics
|
||||
:formatted formatted
|
||||
:local-state *my-dynamic-context*}}
|
||||
|
||||
;; `log!` - [msg] or [level-or-opts msg]
|
||||
(t/with-signal (t/log! {:level :info, :id ::my-id} "Hi!"))
|
||||
;; Message string or vector to join as string
|
||||
["Something interesting happened!" formatted])
|
||||
|
||||
;; `event!` - [id] or [id level-or-opts]
|
||||
(t/with-signal (t/event! ::my-id {:level :info, :msg "Hi!"}))
|
||||
;;;; README "More examples"
|
||||
|
||||
;; `signal!` - [opts]
|
||||
(t/with-signal (t/signal! {:level :info, :id ::my-id, :msg "Hi!"}))
|
||||
;; Set minimum level
|
||||
(t/set-min-level! :warn) ; For all signals
|
||||
(t/set-min-level! :log :debug) ; For `log!` signals only
|
||||
|
||||
;; See `t/help:signal-creators` docstring for more
|
||||
;; Set namespace and id filters
|
||||
(t/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
|
||||
(t/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})
|
||||
|
||||
;;; 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 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 for particular ns pattern
|
||||
(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.
|
||||
;; Use middleware to:
|
||||
;; - Transform signals
|
||||
;; - Filter signals by arb conditions (incl. data/content)
|
||||
|
||||
(t/set-middleware!
|
||||
(fn [signal]
|
||||
(if (get-in signal [:data :hide-me?])
|
||||
nil ; Suppress signal (don't handle)
|
||||
(if (-> signal :data :skip-me?)
|
||||
nil ; Filter 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}})) ; => {...}
|
||||
(t/with-signal (t/event! ::my-id {:data {:skip-me? true}})) ; => nil
|
||||
(t/with-signal (t/event! ::my-id {:data {:skip-me? false}})) ; => {...}
|
||||
|
||||
;;;; Docstring snippets
|
||||
;; Signal handlers
|
||||
|
||||
(t/get-handlers) ; => {<handler-id> {:keys [handler-fn handler-stats_ dispatch-opts]}}
|
||||
|
||||
(t/add-handler! :my-console-handler
|
||||
(t/handler:console {}) ; Returns handler fn, has many opts
|
||||
{:async {:mode :dropping, :buffer-size 1024, :n-threads 1}
|
||||
:priority 100
|
||||
:sample-rate 0.5
|
||||
:min-level :info
|
||||
:ns-filter {:disallow "taoensso.*"}
|
||||
:rate-limit {"1 per sec" [1 1000]}
|
||||
;; See `t/help:handler-dispatch-options` for more
|
||||
})
|
||||
|
||||
;;;; Docstring examples
|
||||
|
||||
(t/with-signal (t/event! ::my-id))
|
||||
(t/with-signal (t/event! ::my-id :warn))
|
||||
|
|
@ -162,7 +174,7 @@
|
|||
|
||||
;; Test handler, remember it's just a (fn [signal])
|
||||
(my-handler my-signal) ; %>
|
||||
;; 2024-04-11T10:54:57.202869Z INFO LOG Schrebermann.local examples(56,1) ::my-id - My message
|
||||
;; 2024-04-11T10:54:57.202869Z INFO LOG MyHost examples(56,1) ::my-id - My message
|
||||
;; data: {:x1 :x2}
|
||||
|
||||
;; Create console handler which writes signals as edn
|
||||
|
|
@ -306,3 +318,33 @@
|
|||
;; {:my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"}
|
||||
;; ... }
|
||||
|
||||
;;;; Misc extra examples
|
||||
|
||||
(t/log! {:id ::my-id, :data {:x1 :x2}} ["My 2-part" "message"]) ; %>
|
||||
;; 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
|
||||
(t/log!
|
||||
{: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
|
||||
(t/log!
|
||||
{:sample-rate 0.5
|
||||
:rate-limit {"1 per sec" [1 1000]}}
|
||||
"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:
|
||||
|
||||
(t/log! {:level :info, :id ::my-id} "Hi!") ; [msg] or [level-or-opts msg]
|
||||
(t/event! ::my-id {:level :info, :msg "Hi!"}) ; [id] or [id level-or-opts]
|
||||
(t/signal! {:level :info, :id ::my-id, :msg "Hi!"}) ; [opts]
|
||||
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Signals are maps with {:keys [inst id ns level data msg_ ...]},
|
||||
though they can be modified by signal and/or handler middleware.
|
||||
Signals are maps with {:keys [inst id ns level data msg_ ...]}, though they
|
||||
can be modified by signal and/or handler middleware.
|
||||
|
||||
Default signal keys:
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ Signal options (shared by all signal creators):
|
|||
handler output, so a great way to provide custom data/opts for use
|
||||
(only) by custom middleware/handlers.
|
||||
|
||||
handler-specific data that can just be ignored by other handlers
|
||||
|
||||
If anything is unclear, please ping me (@ptaoussanis) so that I can improve these docs!
|
||||
|
||||
[1] `java.time.Instant` or `js/Date`
|
||||
|
|
|
|||
|
|
@ -646,6 +646,14 @@
|
|||
(require '[jsonista.core :as jsonista])
|
||||
(pr-signal-fn {:pr-fn jsonista/write-value-as-string ...})
|
||||
|
||||
Motivation:
|
||||
Why use this util instead of just directly using the print function
|
||||
given to `:pr-fn`? Signals are optimized for cheap creation and easy handling,
|
||||
so may contain things like nil values and duplicated content.
|
||||
|
||||
This util efficiently clean signals of such noise, helping reduce
|
||||
storage/transmission size, and making key info easier to see.
|
||||
|
||||
See also `format-signal-fn` for human-readable output."
|
||||
([] (pr-signal-fn nil))
|
||||
([{:keys [pr-fn, incl-kvs? incl-nils? incl-newline? incl-keys] :as opts
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ Its key function is to help:
|
|||
1. **Capture data** in your running Clojure/Script programs, and
|
||||
2. **Facilitate processing** of that data into **useful information / insight**.
|
||||
|
||||
> [Terminology] *Telemetry* derives from the Greek *tele* (remote) and *metron* (measure). It refers to the collection of *in situ* (in position) data, for transmission to other systems for monitoring/analysis. *Logs* are the most common form of software telemetry. So think of telemetry as the *superset of logging-like activities* that help monitor and understand (software) systems.
|
||||
|
||||
## Signals
|
||||
|
||||
The basic unit of data in Telemere is the **signal**.
|
||||
|
|
@ -95,29 +97,30 @@ See section [3-Config](./3-Config) for customization.
|
|||
|
||||
> Signal handlers process created signals to *do something with them* (analyse them, write them to console/file/queue/db, etc.)
|
||||
|
||||
| Platform | Condition | Handler |
|
||||
| -------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Clj | Always | [Console handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) that prints signals to `*out*` or `*err*`. |
|
||||
| Cljs | Always | [Console handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) that prints signals to the **browser console**. |
|
||||
| Platform | Condition | Handler |
|
||||
| -------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Clj | Always | [Console handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) that prints signals to `*out*` or `*err*` |
|
||||
| Cljs | Always | [Console handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) that prints signals to the **browser console** |
|
||||
|
||||
**Default interop**:
|
||||
|
||||
> Telemere can create signals from relevant **external API calls**, etc.
|
||||
|
||||
| Platform | Condition | Signals from |
|
||||
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
||||
| Clj | [SLF4J API](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) and [Telemere SLF4J backend](https://clojars.org/com.taoensso/slf4j-telemere) present | [SLF4J](https://www.slf4j.org/) logging calls. |
|
||||
| Clj | [tools.logging](https://mvnrepository.com/artifact/org.clojure/tools.logging) present and [`tools-logging->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.tools-logging#tools-logging-%3Etelemere!) called | [tools.logging](https://github.com/clojure/tools.logging) logging calls. |
|
||||
| Clj | [`streams->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#streams-%3Etelemere!) called | Output to `System/out` and `System/err` streams. |
|
||||
| Platform | Condition | Signals from |
|
||||
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
|
||||
| Clj | [SLF4J API](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) and [Telemere SLF4J backend](https://clojars.org/com.taoensso/slf4j-telemere) present | [SLF4J](https://www.slf4j.org/) logging calls |
|
||||
| Clj | [tools.logging](https://mvnrepository.com/artifact/org.clojure/tools.logging) present and [`tools-logging->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.tools-logging#tools-logging-%3Etelemere!) called | [tools.logging](https://github.com/clojure/tools.logging) logging calls |
|
||||
| Clj | [`streams->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#streams-%3Etelemere!) called | Output to `System/out` and `System/err` streams |
|
||||
|
||||
Run [`check-interop`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-interop) to help verify/debug:
|
||||
Interop can be tough to get configured correctly so the [`check-interop`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-interop) util is provided to help verify for tests or debugging:
|
||||
|
||||
```clojure
|
||||
(check-interop) ; =>
|
||||
{:tools-logging {:present? false}
|
||||
:slf4j {:sending->telemere? true, :telemere-receiving? true}
|
||||
:system/out {:sending->telemere? false, :telemere-receiving? false}
|
||||
:system/err {:sending->telemere? false, :telemere-receiving? false}}
|
||||
{:tools-logging {:present? false}
|
||||
:slf4j {:present? true, :telemere-receiving? true, ...}
|
||||
:open-telemetry {:present? true, :use-tracer? false, ...}
|
||||
:system/out {:telemere-receiving? false, ...}
|
||||
:system/err {:telemere-receiving? false, ...}}
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
|
@ -197,12 +200,12 @@ Quick examples of some basic filtering:
|
|||
|
||||
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) | 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 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. |
|
||||
| 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) | 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 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 |
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@ Verify successful interop with [`check-interop`](https://cljdoc.org/d/com.taoens
|
|||
## OpenTelemetry
|
||||
|
||||
> [OpenTelemetry](https://opentelemetry.io/) is a popular open-source observability framework that provides tools for collecting, processing, and exporting telemetry data like traces, metrics, and logs from software systems.
|
||||
>
|
||||
> Telemere's OpenTelemetry interop is **experimental** - I'm looking for [feedback](https://www.taoensso.com/telemere/slack) on this feature please! 🙏
|
||||
|
||||
Telemere can send signals as [`LogRecords`](https://opentelemetry.io/docs/specs/otel/logs/data-model/) with correlated tracing data to configured [OpenTelemetry Java](https://github.com/open-telemetry/opentelemetry-java) [exporters](https://opentelemetry.io/docs/languages/java/exporters/).
|
||||
|
||||
|
|
@ -94,14 +96,13 @@ This allows output to go (via configured exporters) to a wide variety of targets
|
|||
|
||||
To do this:
|
||||
|
||||
1. Ensure that you have the [OpenTelemetry Java](https://github.com/open-telemetry/opentelemetry-java) dependency.
|
||||
1. Ensure that you have the necessary [OpenTelemetry Java](https://github.com/open-telemetry/opentelemetry-java) dependencies.
|
||||
2. Ensure that the relevant exporters are [appropriately configured](https://opentelemetry.io/docs/languages/java/configuration/) (this is the trickiest part, but not at all specific to Telemere).
|
||||
3. Create a Telemere signal handler using [`handler:open-telemetry`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry), and register it using [`add-handler!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#add-handler!).
|
||||
4. Ensure that [`otel-tracing?`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#otel-tracing?) is enabled if you want tracing interop.
|
||||
|
||||
Aside from configuring the exporters (2), Telemere's OpenTelemetry interop **does not require** any use of or familiarity with the OpenTelemetry Java API or concepts. Just use Telemere as you normally would, and the handler (3) will automatically emit detailed log and trace data to your configured exporters (2).
|
||||
|
||||
> **OpenTelemetry interop is experimental** - I'm looking for feedback on this feature please! 🙏
|
||||
|
||||
## Tufte
|
||||
|
||||
> [Tufte](https:/www.taoensso.com/tufte) is a simple performance monitoring library for Clojure/Script by the author of Telemere.
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
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).
|
||||
Telemere includes a number of signal handlers out-the-box, and more may be available via the [community](./8-Community#handlers-and-tools).
|
||||
|
||||
You can also easily [write your own handlers](#writing-handlers) for any output or integration you need.
|
||||
|
||||
# Included handlers
|
||||
|
||||
> See ✅ links for **features and usage**
|
||||
> See 👍 links to **vote on handler** for future addition
|
||||
See ✅ links below for **features and usage**,
|
||||
See 👍 links below to **vote on future handlers**:
|
||||
|
||||
| Target (↓) | Clj | Cljs |
|
||||
| :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: |
|
||||
| [Apache Kafka](https://kafka.apache.org/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [AWS Kinesis](https://aws.amazon.com/kinesis/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| Console | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) |
|
||||
| Console (raw) | - | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) |
|
||||
| [Datadog](https://www.datadoghq.com/) | [👍](https://github.com/taoensso/roadmap/issues/12) | [👍](https://github.com/taoensso/roadmap/issues/12) |
|
||||
| Email | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) | - |
|
||||
| [Graylog](https://graylog.org/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [Jaeger](https://www.jaegertracing.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [Logstash](https://www.elastic.co/logstash) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [OpenTelemetry](https://opentelemetry.io/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry) | [👍](https://github.com/taoensso/roadmap/issues/12) |
|
||||
| [Redis](https://redis.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| SQL | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [Slack](https://slack.com/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.slack#handler:slack) | - |
|
||||
| TCP socket | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:tcp-socket) | - |
|
||||
| UDP socket | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:udp-socket) | - |
|
||||
| [Zipkin](https://zipkin.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| Target (↓) | Clj | Cljs |
|
||||
| :--------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: |
|
||||
| [Apache Kafka](https://kafka.apache.org/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [AWS Kinesis](https://aws.amazon.com/kinesis/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| Console | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) |
|
||||
| Console (raw) | - | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) |
|
||||
| [Datadog](https://www.datadoghq.com/) | [👍](https://github.com/taoensso/roadmap/issues/12) | [👍](https://github.com/taoensso/roadmap/issues/12) |
|
||||
| Email | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) | - |
|
||||
| [Graylog](https://graylog.org/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [Jaeger](https://www.jaegertracing.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [Logstash](https://www.elastic.co/logstash) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [OpenTelemetry](https://opentelemetry.io/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry) | [👍](https://github.com/taoensso/roadmap/issues/12) |
|
||||
| [Redis](https://redis.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| SQL | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [Slack](https://slack.com/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.slack#handler:slack) | - |
|
||||
| TCP socket | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:tcp-socket) | - |
|
||||
| UDP socket | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:udp-socket) | - |
|
||||
| [Zipkin](https://zipkin.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
|
||||
# Configuring handlers
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ By default it writes formatted strings intended for human consumption:
|
|||
|
||||
;; Test handler, remember it's just a (fn [signal])
|
||||
(my-handler my-signal) ; %>
|
||||
;; 2024-04-11T10:54:57.202869Z INFO LOG Schrebermann.local examples(56,1) ::my-id - My message
|
||||
;; 2024-04-11T10:54:57.202869Z INFO LOG MyHost examples(56,1) ::my-id - My message
|
||||
;; data: {:x1 :x2}
|
||||
```
|
||||
|
||||
|
|
@ -243,7 +243,7 @@ If you're making a customizable handler for use by others, it's often handy to d
|
|||
[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
|
||||
2024-04-11T10:54:57.202869Z INFO LOG MyHost examples(56,1) ::my-id - My message
|
||||
data: {:x1 :x2}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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#handlers).
|
||||
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-and-tools).
|
||||
|
||||
If not, you may need to [write something yourself](./4-Handlers#writing-handlers).
|
||||
|
||||
|
|
|
|||
|
|
@ -4,22 +4,22 @@ My plan for Telemere is to offer a **stable core of limited scope**, then to foc
|
|||
|
||||
If you spot issues with any linked resources, please **contact the relevant authors** to let them know! Thank you! 🙏 - [Peter](https://www.taoensso.com)
|
||||
|
||||
# Handlers
|
||||
# Handlers and tools
|
||||
|
||||
Includes libraries or examples for handlers (see [Writing handlers](./4-Handlers#writing-handlers)), middleware, handler utils (e.g. formatters), etc.:
|
||||
Includes libraries or examples for handlers (see [Writing handlers](./4-Handlers#writing-handlers)), middleware, handler utils (e.g. formatters), tools for analyzing signals, etc.:
|
||||
|
||||
| Date | Link | Description |
|
||||
| :--- | :--- | :------------------------------------------------------------ |
|
||||
| - | - | Your link here? [PRs](../wiki#contributions-welcome) welcome! |
|
||||
| Date | Link | Type | 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) |
|
||||
| Date | Link | Type | Description |
|
||||
| :--------- | :---------------------------------------------------------------- | ------- | :----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| - | - | - | Your link here? [PRs](../wiki#contributions-welcome) welcome! |
|
||||
| - | [Official Slack channel](https://www.taoensso.com/telemere/slack) | Support | For questions, support, etc. |
|
||||
| - | [GitHub issues](https://github.com/taoensso/telemere/issues) | Support | 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) | Demo | Official Telemere announcement demo (24 mins) |
|
||||
|
|
|
|||
Loading…
Reference in a new issue