mirror of
https://github.com/taoensso/telemere.git
synced 2025-12-17 18:01:10 +00:00
[doc] Misc improvements
This commit is contained in:
parent
f9564b2fc5
commit
e4a0a41a1b
13 changed files with 142 additions and 127 deletions
113
README.md
113
README.md
|
|
@ -5,15 +5,20 @@
|
||||||
|
|
||||||
### Structured telemetry library for Clojure/Script
|
### Structured telemetry library for Clojure/Script
|
||||||
|
|
||||||
**Telemere** is a next-generation replacement for [Timbre](https://www.taoensso.com/timbre) that offers one simple **unified API** for **traditional logging**, **structured logging**, **tracing**, and **basic performance monitoring**.
|
**Telemere** is a **pure Clojure/Script library** that offers an elegant and simple **unified API** to cover:
|
||||||
|
|
||||||
Friendly enough for complete beginners, but flexible enough for the most complex and performance-sensitive real-world projects.
|
- **Traditional logging** (string messages)
|
||||||
|
- **Structured logging** (rich Clojure data types and structures)
|
||||||
|
- **Events** (named thing happened, with optional data)
|
||||||
|
- **Tracing** (nested flow tracking, with optional data)
|
||||||
|
- Basic **performance monitoring** (nested form runtimes)
|
||||||
|
- Any combination of the above
|
||||||
|
|
||||||
It helps enable Clojure/Script systems that are easily **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.
|
It's small, *super* fast, easy to learn, easy to use, and **absurdly flexible**.
|
||||||
|
|
||||||
Supports Clojure, ClojureScript, [GraalVM](https://en.wikipedia.org/wiki/GraalVM), but not ([yet](../../wiki/6-FAQ#does-telemere-work-with-babashka)) [Babashka](https://github.com/babashka/babashka).
|
It helps enable Clojure/Script systems that are easily **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) and [Truss](https://www.taoensso.com/truss).
|
||||||
|
|
||||||
See [here](../../wiki/1-Getting-started) for **full introduction**.
|
See [here](../../wiki/1-Getting-started) for **full introduction** (concepts, terminology, getting started).
|
||||||
|
|
||||||
## Latest release/s
|
## Latest release/s
|
||||||
|
|
||||||
|
|
@ -22,7 +27,21 @@ See [here](../../wiki/1-Getting-started) for **full introduction**.
|
||||||
[![Main tests][Main tests SVG]][Main tests URL]
|
[![Main tests][Main tests SVG]][Main tests URL]
|
||||||
[![Graal tests][Graal tests SVG]][Graal tests URL]
|
[![Graal tests][Graal tests SVG]][Graal tests URL]
|
||||||
|
|
||||||
See [here][GitHub releases] for earlier releases.
|
<!--See [here][GitHub releases] for earlier releases.-->
|
||||||
|
|
||||||
|
## 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**.
|
||||||
|
|
||||||
## Quick examples
|
## Quick examples
|
||||||
|
|
||||||
|
|
@ -39,7 +58,7 @@ See [here][GitHub releases] for earlier releases.
|
||||||
(t/log! {:level :info, :data {...}} "Hello again!")
|
(t/log! {:level :info, :data {...}} "Hello again!")
|
||||||
(t/event! ::my-id {:level :debug, :data {...}})
|
(t/event! ::my-id {:level :debug, :data {...}})
|
||||||
|
|
||||||
;; Trace (auto interops with OpenTelemetry)
|
;; Trace (can interop with OpenTelemetry)
|
||||||
;; Tracks form runtime, return value, and (nested) parent tree
|
;; Tracks form runtime, return value, and (nested) parent tree
|
||||||
(t/trace! {:id ::my-id :data {...}}
|
(t/trace! {:id ::my-id :data {...}}
|
||||||
(do-some-work))
|
(do-some-work))
|
||||||
|
|
@ -56,7 +75,7 @@ See [here][GitHub releases] for earlier releases.
|
||||||
;; Getting fancy (all costs are conditional!)
|
;; Getting fancy (all costs are conditional!)
|
||||||
(t/log!
|
(t/log!
|
||||||
{:level :debug
|
{:level :debug
|
||||||
:sample-rate (my-dynamic-sample-rate)
|
:sample-rate 0.75 ; 75% sampling (noop 25% of the time)
|
||||||
:when (my-conditional)
|
:when (my-conditional)
|
||||||
:rate-limit {"1 per sec" [1 1000]
|
:rate-limit {"1 per sec" [1 1000]
|
||||||
"5 per min" [5 60000]}
|
"5 per min" [5 60000]}
|
||||||
|
|
@ -85,13 +104,13 @@ See [here][GitHub releases] for earlier releases.
|
||||||
|
|
||||||
### 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).
|
- 1st-class **out-the-box interop** with [tools.logging](../../wiki/3-Config#toolslogging), [Java logging via SLF4J v2](../../wiki/3-Config#java-logging), [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).
|
- 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.
|
- Extensive set of [handlers](../../wiki/4-Handlers#included-handlers) included out-the-box.
|
||||||
|
|
||||||
### Scaling
|
### Scaling
|
||||||
|
|
||||||
- Hyper-optimized and **blazing fast**, see [benchmarks](#benchmarks).
|
- Hyper-optimized and **blazing fast**, see [performance](#performance).
|
||||||
- An API that **scales comfortably** from the smallest disposable code, to the most massive and complex real-world production environments.
|
- 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.
|
- 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.
|
||||||
|
|
||||||
|
|
@ -108,20 +127,6 @@ See [here][GitHub releases] for earlier releases.
|
||||||
- Telemere [compared](../../wiki/5-Migrating#from-timbre) to [Timbre](https://www.taoensso.com/timbre) (Telemere's predecessor)
|
- 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)
|
- 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
|
## Video demo
|
||||||
|
|
||||||
See for intro and basic usage:
|
See for intro and basic usage:
|
||||||
|
|
@ -226,7 +231,36 @@ Detailed help is available without leaving your IDE:
|
||||||
| [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) | Signal handler dispatch options |
|
| [`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 |
|
||||||
|
|
||||||
### Included handlers
|
## Performance
|
||||||
|
|
||||||
|
Telemere is **highly optimized** and offers great performance at any scale, handling up to **4.2 million filtered signals/sec** on a 2020 Macbook Pro M1.
|
||||||
|
|
||||||
|
Signal call benchmarks (per thread):
|
||||||
|
|
||||||
|
| Compile-time filtering? | Runtime filtering? | Profile? | Trace? | nsecs / call |
|
||||||
|
| :---------------------: | :----------------: | :------: | :----: | -----------: |
|
||||||
|
| ✓ (elide) | - | - | - | 0 |
|
||||||
|
| - | ✓ | - | - | 350 |
|
||||||
|
| - | ✓ | ✓ | - | 450 |
|
||||||
|
| - | ✓ | ✓ | ✓ | 1000 |
|
||||||
|
|
||||||
|
- Nanoseconds per signal call ~ **milliseconds per 1e6 calls**
|
||||||
|
- Times exclude [handler runtime](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats) (which depends on handler/s, is usually async)
|
||||||
|
- Benched on a 2020 Macbook Pro M1, running Clojure v1.12 and OpenJDK v22
|
||||||
|
|
||||||
|
### Performance philosophy
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Telemere is designed to help with all that. It offers [rich data](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) and unmatched [filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) support - including per-signal and per-handler **sampling** and **rate-limiting**, and zero cost compile-time filtering.
|
||||||
|
|
||||||
|
Use these to ensure that you're not capturing useless/low-value/high-noise information in production! With appropriate planning, Telemere is designed to scale to systems of any size and complexity.
|
||||||
|
|
||||||
|
See [here](../../wiki/7-Tips) for detailed tips on real-world usage.
|
||||||
|
|
||||||
|
## Included handlers
|
||||||
|
|
||||||
See ✅ links below for **features and usage**,
|
See ✅ links below for **features and usage**,
|
||||||
See ❤️ links below to **vote on future handlers**:
|
See ❤️ links below to **vote on future handlers**:
|
||||||
|
|
@ -266,35 +300,6 @@ See [here](../../wiki/8-Community) for community resources.
|
||||||
- Support via [Slack channel][] or [GitHub issues][]
|
- Support via [Slack channel][] or [GitHub issues][]
|
||||||
- [General observability tips](../../wiki/7-Tips) (advice on building and maintaining observable Clojure/Script systems, and getting the most out of Telemere)
|
- [General observability tips](../../wiki/7-Tips) (advice on building and maintaining observable Clojure/Script systems, and getting the most out of Telemere)
|
||||||
|
|
||||||
## Benchmarks
|
|
||||||
|
|
||||||
Telemere is **highly optimized** and offers great performance at any scale:
|
|
||||||
|
|
||||||
| Compile-time filtering? | Runtime filtering? | Profile? | Trace? | nsecs |
|
|
||||||
| :---------------------: | :----------------: | :------: | :----: | ----: |
|
|
||||||
| ✓ (elide) | - | - | - | 0 |
|
|
||||||
| - | ✓ | - | - | 350 |
|
|
||||||
| - | ✓ | ✓ | - | 450 |
|
|
||||||
| - | ✓ | ✓ | ✓ | 1000 |
|
|
||||||
|
|
||||||
Measurements:
|
|
||||||
|
|
||||||
- Are **~nanoseconds per signal call** (= milliseconds per 1e6 calls)
|
|
||||||
- Exclude [handler runtime](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats) (which depends on handler/s, is usually async)
|
|
||||||
- Taken on a 2020 Macbook Pro M1, running Clojure v1.12 and OpenJDK v22
|
|
||||||
|
|
||||||
### Performance philosophy
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Telemere is designed to help with all that. It offers [rich data](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) and unmatched [filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) support - including per-signal and per-handler **sampling** and **rate-limiting**.
|
|
||||||
|
|
||||||
Use these to ensure that you're not capturing useless/low-value/high-noise information in production! With appropriate planning, Telemere is designed to scale to systems of any size and complexity.
|
|
||||||
|
|
||||||
See [here](../../wiki/7-Tips) for detailed tips on real-world usage.
|
|
||||||
|
|
||||||
## Funding
|
## Funding
|
||||||
|
|
||||||
You can [help support][sponsor] continued work on this project, thank you!! 🙏
|
You can [help support][sponsor] continued work on this project, thank you!! 🙏
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
;; Getting fancy (all costs are conditional!)
|
;; Getting fancy (all costs are conditional!)
|
||||||
(t/log!
|
(t/log!
|
||||||
{:level :debug
|
{:level :debug
|
||||||
:sample-rate (my-dynamic-sample-rate)
|
:sample-rate 0.75 ; 75% sampling (noop 25% of the time)
|
||||||
:when (my-conditional)
|
:when (my-conditional)
|
||||||
:rate-limit {"1 per sec" [1 1000]
|
:rate-limit {"1 per sec" [1 1000]
|
||||||
"5 per min" [5 60000]}
|
"5 per min" [5 60000]}
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,7 @@
|
||||||
"Experimental, subject to change.
|
"Experimental, subject to change.
|
||||||
Minimal Telemere facade API for library authors, etc.
|
Minimal Telemere facade API for library authors, etc.
|
||||||
Allows library code to use Telemere if it's present, or fall back to
|
Allows library code to use Telemere if it's present, or fall back to
|
||||||
something like `tools.logging` otherwise.
|
something like tools.logging otherwise."
|
||||||
|
|
||||||
(ns my-lib
|
|
||||||
(:require
|
|
||||||
[taoensso.telemere.api :as t] ; `com.taoensso/telemere-api` dependency
|
|
||||||
[clojure.tools.logging :as ctl] ; `org.clojure/tools.logging` dependency
|
|
||||||
))
|
|
||||||
|
|
||||||
(t/require-telemere-if-present) ; Just below `ns` form
|
|
||||||
|
|
||||||
;; Optional convenience for library users
|
|
||||||
(defn set-min-level!
|
|
||||||
\"If using Telemere, sets Telemere's minimum level for <library> namespaces.
|
|
||||||
Possible levels: #{:trace :debug :info :warn :error :fatal :report}.
|
|
||||||
Default level: `:warn`.
|
|
||||||
[min-level]
|
|
||||||
(t/if-telemere
|
|
||||||
(do (t/set-min-level! nil \"my-lib(.*)\" min-level) true)
|
|
||||||
false))
|
|
||||||
|
|
||||||
(defonce ^:private __set-default-min-level (set-min-level! :warn))
|
|
||||||
|
|
||||||
(signal!
|
|
||||||
{:kind :log, :id :my-id, :level :warn,
|
|
||||||
:let [x :x]
|
|
||||||
:msg [\"Hello\" \"world\" x]
|
|
||||||
:data {:a :A :x x}
|
|
||||||
:fallback (ctl/warn (str \"Hello world\" x))})"
|
|
||||||
|
|
||||||
{:author "Peter Taoussanis (@ptaoussanis)"}
|
{:author "Peter Taoussanis (@ptaoussanis)"}
|
||||||
#?(:clj (:require [clojure.java.io :as jio])
|
#?(:clj (:require [clojure.java.io :as jio])
|
||||||
:cljs (:require-macros [taoensso.telemere.api :refer [compile-if]])))
|
:cljs (:require-macros [taoensso.telemere.api :refer [compile-if]])))
|
||||||
|
|
@ -87,7 +59,7 @@
|
||||||
Otherwise expands to arbitrary `fallback` opt form.
|
Otherwise expands to arbitrary `fallback` opt form.
|
||||||
|
|
||||||
Allows library code to use Telemere if it's present, or fall back to
|
Allows library code to use Telemere if it's present, or fall back to
|
||||||
something like `tools.logging` otherwise.
|
something like tools.logging otherwise.
|
||||||
|
|
||||||
MUST be used with `require-telemere-if-present`, example:
|
MUST be used with `require-telemere-if-present`, example:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ Tips:
|
||||||
- Test using `with-signal`: (with-signal (catch->error! ...)).
|
- Test using `with-signal`: (with-signal (catch->error! ...)).
|
||||||
- Supports the same options [2] as other signals [1].
|
- Supports the same options [2] as other signals [1].
|
||||||
|
|
||||||
- Useful for recording errors in forms, futures, callbacks, etc.
|
- Useful for preventing errors from going unnoticed in futures, callbacks,
|
||||||
|
agent actions, etc.!: (future (catch->error ::my-future (do-something)))
|
||||||
|
|
||||||
See also `error!`.
|
See also `error!`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ When filtering conditions are met [4], creates a Telemere signal [3] and
|
||||||
dispatches it to registered handlers for processing (e.g. writing to
|
dispatches it to registered handlers for processing (e.g. writing to
|
||||||
console/file/queue/db, etc.).
|
console/file/queue/db, etc.).
|
||||||
|
|
||||||
|
`form` is generally expected to be synchronous and eager (not a lazy seq,
|
||||||
|
async call, or IOT code like a core.async `go` block, etc.).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
(spy! (+ 1 2)) ; %> {:kind :trace, :level :info, :run-form '(+ 1 2),
|
(spy! (+ 1 2)) ; %> {:kind :trace, :level :info, :run-form '(+ 1 2),
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ When filtering conditions are met [4], creates a Telemere signal [3] and
|
||||||
dispatches it to registered handlers for processing (e.g. writing to
|
dispatches it to registered handlers for processing (e.g. writing to
|
||||||
console/file/queue/db, etc.).
|
console/file/queue/db, etc.).
|
||||||
|
|
||||||
|
`form` is generally expected to be synchronous and eager (not a lazy seq,
|
||||||
|
async call, or IOT code like a core.async `go` block, etc.).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
(trace! (+ 1 2)) ; %> {:kind :trace, :level :info, :run-form '(+ 1 2),
|
(trace! (+ 1 2)) ; %> {:kind :trace, :level :info, :run-form '(+ 1 2),
|
||||||
|
|
|
||||||
|
|
@ -758,7 +758,7 @@
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(defmacro signal-allowed?
|
(defmacro signal-allowed?
|
||||||
"Used only for interop (SLF4J, `tools.logging`, etc.)."
|
"Used only for interop (tools.logging, SLF4J, etc.)."
|
||||||
{:arglists (signal-arglists :signal!)}
|
{:arglists (signal-arglists :signal!)}
|
||||||
[opts]
|
[opts]
|
||||||
(let [{:keys [#_expansion-id #_location elide? allow?]}
|
(let [{:keys [#_expansion-id #_location elide? allow?]}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
(ns taoensso.telemere.tools-logging
|
(ns taoensso.telemere.tools-logging
|
||||||
"Interop support for `tools.logging` -> Telemere.
|
"Interop support for tools.logging -> Telemere.
|
||||||
Telemere will attempt to load this ns automatically when possible.
|
Telemere will attempt to load this ns automatically when possible.
|
||||||
|
|
||||||
Naming conventions:
|
Naming conventions:
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
(get-logger [_ logger-name] (TelemereLogger. (str logger-name))))
|
(get-logger [_ logger-name] (TelemereLogger. (str logger-name))))
|
||||||
|
|
||||||
(defn tools-logging->telemere!
|
(defn tools-logging->telemere!
|
||||||
"Configures `tools.logging` to use Telemere as its logging
|
"Configures tools.logging to use Telemere as its logging
|
||||||
implementation (backend).
|
implementation (backend).
|
||||||
|
|
||||||
Called automatically if one of the following is \"true\":
|
Called automatically if one of the following is \"true\":
|
||||||
|
|
@ -53,13 +53,13 @@
|
||||||
{:kind :event
|
{:kind :event
|
||||||
:level :debug ; < :info since runs on init
|
:level :debug ; < :info since runs on init
|
||||||
:id :taoensso.telemere/tools-logging->telemere!
|
:id :taoensso.telemere/tools-logging->telemere!
|
||||||
:msg "Enabling interop: `tools.logging` -> Telemere"})
|
:msg "Enabling interop: tools.logging -> Telemere"})
|
||||||
|
|
||||||
(alter-var-root #'clojure.tools.logging/*logger-factory*
|
(alter-var-root #'clojure.tools.logging/*logger-factory*
|
||||||
(fn [_] (TelemereLoggerFactory.))))
|
(fn [_] (TelemereLoggerFactory.))))
|
||||||
|
|
||||||
(defn tools-logging->telemere?
|
(defn tools-logging->telemere?
|
||||||
"Returns true iff `tools.logging` is configured to use Telemere
|
"Returns true iff tools.logging is configured to use Telemere
|
||||||
as its logging implementation (backend)."
|
as its logging implementation (backend)."
|
||||||
[]
|
[]
|
||||||
(when-let [lf clojure.tools.logging/*logger-factory*]
|
(when-let [lf clojure.tools.logging/*logger-factory*]
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
(let [sending? (tools-logging->telemere?)
|
(let [sending? (tools-logging->telemere?)
|
||||||
receiving?
|
receiving?
|
||||||
(and sending?
|
(and sending?
|
||||||
(impl/test-interop! "`tools.logging` -> Telemere"
|
(impl/test-interop! "tools.logging -> Telemere"
|
||||||
#(clojure.tools.logging/info %)))]
|
#(clojure.tools.logging/info %)))]
|
||||||
|
|
||||||
{:present? true
|
{:present? true
|
||||||
|
|
|
||||||
|
|
@ -657,7 +657,7 @@
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(deftest _interop
|
(deftest _interop
|
||||||
[(testing "`tools.logging` -> Telemere"
|
[(testing "tools.logging -> Telemere"
|
||||||
[(is (sm? (tel/check-interop) {:tools-logging {:present? true, :sending->telemere? true, :telemere-receiving? true}}))
|
[(is (sm? (tel/check-interop) {:tools-logging {:present? true, :sending->telemere? true, :telemere-receiving? true}}))
|
||||||
(is (sm? (with-sig (ctl/info "Hello" "x" "y")) {:level :info, :ns "taoensso.telemere-tests", :kind :tools-logging, :msg_ "Hello x y", :inst pinst?}))
|
(is (sm? (with-sig (ctl/info "Hello" "x" "y")) {:level :info, :ns "taoensso.telemere-tests", :kind :tools-logging, :msg_ "Hello x y", :inst pinst?}))
|
||||||
(is (sm? (with-sig (ctl/warn "Hello" "x" "y")) {:level :warn, :ns "taoensso.telemere-tests", :kind :tools-logging, :msg_ "Hello x y", :inst pinst?}))
|
(is (sm? (with-sig (ctl/warn "Hello" "x" "y")) {:level :warn, :ns "taoensso.telemere-tests", :kind :tools-logging, :msg_ "Hello x y", :inst pinst?}))
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,11 @@ See section [4-Handlers](./4-Handlers).
|
||||||
|
|
||||||
## tools.logging
|
## tools.logging
|
||||||
|
|
||||||
[`tools.logging`](https://github.com/clojure/tools.logging) can use Telemere as its logging implementation (backend). This'll let `tools.logging` calls create Telemere signals.
|
[tools.logging](https://github.com/clojure/tools.logging) can use Telemere as its logging implementation (backend). This'll let tools.logging calls create Telemere signals.
|
||||||
|
|
||||||
To do this:
|
To do this:
|
||||||
|
|
||||||
1. Ensure that you have the `tools.logging` dependency, and
|
1. Ensure that you have the tools.logging [dependency](https://mvnrepository.com/artifact/org.clojure/tools.logging), and
|
||||||
2. Call [`tools-logging->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.tools-logging#tools-logging-%3Etelemere!), or set the relevant environmental config as described in its docstring.
|
2. Call [`tools-logging->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.tools-logging#tools-logging-%3Etelemere!), or set the relevant environmental config as described in its docstring.
|
||||||
|
|
||||||
Verify successful interop with [`check-interop`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-interop):
|
Verify successful interop with [`check-interop`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-interop):
|
||||||
|
|
@ -41,18 +41,14 @@ Verify successful interop with [`check-interop`](https://cljdoc.org/d/com.taoens
|
||||||
|
|
||||||
## Java logging
|
## Java logging
|
||||||
|
|
||||||
[`SLF4Jv2`](https://www.slf4j.org/) can use Telemere as its logging backend. This'll let SLF4J logging calls create Telemere signals.
|
[SLF4Jv2](https://www.slf4j.org/) can use Telemere as its logging backend. This'll let SLF4J logging calls create Telemere signals.
|
||||||
|
|
||||||
To do this, ensure that you have the following dependencies:
|
To do this:
|
||||||
|
|
||||||
```clojure
|
1. Ensure that you have the SLF4J [dependency](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) ( v2+ **only**), and
|
||||||
[org.slf4j/slf4j-api "x.y.z"] ; >= 2.0.0 only!
|
2. Ensure that you have the Telemere SLF4J backend [dependency](https://clojars.org/com.taoensso/telemere-slf4j)
|
||||||
[com.taoensso/telemere-slf4j "x.y.z"]
|
|
||||||
```
|
|
||||||
|
|
||||||
> Telemere needs SLF4J API **version 2 or newer**. If you're seeing `Failed to load class "org.slf4j.impl.StaticLoggerBinder"` it could be that your project is importing the older v1 API, check with `lein deps :tree` or equivalent.
|
When `com.taoensso/telemere-slf4j` (2) is on your classpath AND no other SLF4J backends are, SLF4J will automatically direct all its logging calls to Telemere.
|
||||||
|
|
||||||
When `com.taoensso/telemere-slf4j` is on your classpath AND no other SLF4J backends are, SLF4J will direct all its logging calls to Telemere.
|
|
||||||
|
|
||||||
Verify successful interop with [`check-interop`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-interop):
|
Verify successful interop with [`check-interop`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-interop):
|
||||||
|
|
||||||
|
|
@ -61,7 +57,9 @@ Verify successful interop with [`check-interop`](https://cljdoc.org/d/com.taoens
|
||||||
{:slf4j {:sending->telemere? true, :telemere-receiving? true}}
|
{:slf4j {:sending->telemere? true, :telemere-receiving? true}}
|
||||||
```
|
```
|
||||||
|
|
||||||
For other (non-SLF4J) logging like [Log4j](https://logging.apache.org/log4j/2.x/), [`java.util.logging`](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html) (JUL), and [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/) (JCL), use an appropriate [SLF4J bridge](https://www.slf4j.org/legacy.html) and the normal SLF4J config as above.
|
> Telemere needs SLF4J API **version 2 or newer**. If you're seeing `Failed to load class "org.slf4j.impl.StaticLoggerBinder"` it could be that your project is importing the older v1 API, check with `lein deps :tree` or equivalent.
|
||||||
|
|
||||||
|
For other (non-SLF4J) logging like [Log4j](https://logging.apache.org/log4j/2.x/), [java.util.logging](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html) (JUL), and [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/) (JCL), use an appropriate [SLF4J bridge](https://www.slf4j.org/legacy.html) and the normal SLF4J config as above.
|
||||||
|
|
||||||
In this case logging will be forwarded:
|
In this case logging will be forwarded:
|
||||||
|
|
||||||
|
|
@ -96,7 +94,7 @@ This allows output to go (via configured exporters) to a wide variety of targets
|
||||||
|
|
||||||
To do this:
|
To do this:
|
||||||
|
|
||||||
1. Ensure that you have the necessary [OpenTelemetry Java](https://github.com/open-telemetry/opentelemetry-java) dependencies.
|
1. Ensure that you have the necessary [OpenTelemetry Java](https://github.com/open-telemetry/opentelemetry-java) [dependency](https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-api).
|
||||||
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).
|
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!).
|
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.
|
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.
|
||||||
|
|
@ -117,7 +115,7 @@ Telemere can easily incorporate Tufte performance data in its signals, just like
|
||||||
Telemere and Tufte work great together:
|
Telemere and Tufte work great together:
|
||||||
|
|
||||||
- Their functionality is complementary.
|
- Their functionality is complementary.
|
||||||
- The [upcoming](https:/www.taoensso.com/roadmap) Tufte v4 will share the same core as Telemere and offer an **identical API** for managing filters and handlers.
|
- The [upcoming](https:/www.taoensso.com/roadmap) Tufte v3 will share the same core as Telemere and offer an **identical API** for managing filters and handlers.
|
||||||
|
|
||||||
## Truss
|
## Truss
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ 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:
|
Telemere's functionality is a **superset of Timbre**, and offers *many* improvements including:
|
||||||
|
|
||||||
- Better support for [structured logging](./1-Getting-started#data-types-and-structures)
|
- Better support for [structured logging](./1-Getting-started#data-types-and-structures)
|
||||||
- Better [performance](https://github.com/taoensso/telemere#benchmarks)
|
- Much better [performance](https://github.com/taoensso/telemere#performance)
|
||||||
- Better [documentation](https://github.com/taoensso/telemere#documentation)
|
- Much better [documentation](https://github.com/taoensso/telemere#documentation)
|
||||||
- Better [included handlers](./4-Handlers##included-handlers)
|
|
||||||
- A more flexible [API](./1-Getting-started#usage) that unifies all telemetry and logging needs
|
- A more flexible [API](./1-Getting-started#usage) that unifies all telemetry and logging needs
|
||||||
- A more robust [architecture](./2-Architecture), free from all historical constraints
|
- A more robust [architecture](./2-Architecture), free from all historical constraints
|
||||||
|
- Better [included handlers](./4-Handlers##included-handlers)
|
||||||
- Easier [configuration](./3-Config)
|
- Easier [configuration](./3-Config)
|
||||||
|
|
||||||
Migrating from Timbre to Telemere should be straightforward **unless you depend on specific/custom appenders** that might not be available for Telemere (yet).
|
Migrating from Timbre to Telemere should be straightforward **unless you depend on specific/custom appenders** that might not be available for Telemere (yet).
|
||||||
|
|
@ -63,7 +63,7 @@ If for any reason your tests are unsuccessful, please don't feel pressured to mi
|
||||||
|
|
||||||
# From tools.logging
|
# From tools.logging
|
||||||
|
|
||||||
This is easy, see [here](./3-Config#clojuretoolslogging).
|
This is easy, see [here](./3-Config#toolslogging).
|
||||||
|
|
||||||
# From Java logging
|
# From Java logging
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ This will eventually ease long-term maintenance, increase reliability, and help
|
||||||
|
|
||||||
> [Tufte](https:/www.taoensso.com/tufte) is a simple performance monitoring library for Clojure/Script by the author of Telemere.
|
> [Tufte](https:/www.taoensso.com/tufte) is a simple performance monitoring library for Clojure/Script by the author of Telemere.
|
||||||
|
|
||||||
**No**, Telemere does **not** replace [Tufte](https:/www.taoensso.com/tufte). They work great together, and the [upcoming](https:/www.taoensso.com/roadmap) Tufte v4 will share the same core as Telemere and offer an **identical API** for managing filters and handlers.
|
**No**, Telemere does **not** replace [Tufte](https:/www.taoensso.com/tufte). They work great together, and the [upcoming](https:/www.taoensso.com/roadmap) Tufte v3 will share the same core as Telemere and offer an **identical API** for managing filters and handlers.
|
||||||
|
|
||||||
There is **some feature overlap** though since Telemere offers basic performance measurement as part of its tracing features.
|
There is **some feature overlap** though since Telemere offers basic performance measurement as part of its tracing features.
|
||||||
|
|
||||||
|
|
@ -54,13 +54,13 @@ They're focused on complementary things. When both are in use:
|
||||||
|
|
||||||
> [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.
|
> [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.
|
**Yes**, this shouldn't be a problem.
|
||||||
|
|
||||||
# Does Telemere work with Babashka?
|
# Does Telemere work with Babashka?
|
||||||
|
|
||||||
> [Babashka](https://github.com/babashka/babashka) is a native Clojure interpreter for scripting with fast startup.
|
> [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).
|
**No**, 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.
|
If there's interest in this, please [upvote](https://github.com/taoensso/roadmap/issues/22) on my open source roadmap.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,19 @@
|
||||||
Are you a library author/maintainer that's considering **using Telemere in your library**?
|
Are you a library author/maintainer that's considering **using Telemere in your library**?
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
## 1. Consider a basic facade
|
## 1. Common logging facade (basic logging only)
|
||||||
|
|
||||||
Does your library **really need** Telemere? Many libraries only need very basic logging. In these cases it can be beneficial to do your logging through a common basic facade like [tools.logging](https://github.com/clojure/tools.logging) or [SLF4J](https://www.slf4j.org/).
|
Many libraries need only basic logging. In these cases it can be beneficial to do your logging through a common logging facade like [tools.logging](https://github.com/clojure/tools.logging) or [SLF4J](https://www.slf4j.org/).
|
||||||
|
|
||||||
**Pro**: users can then choose and configure their **preferred backend** - including Telemere, which can easily [act as a backend](./3-Config#interop) for both tools.logging and SLF4J.
|
This'll limit you to basic features (e.g. no structured logging or [rich filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-filters)) - but your users will have the freedom to choose and configure their **preferred backend** ([incl. Telemere if they like](./3-Config#interop)).
|
||||||
|
|
||||||
**Cons**: you'll be limited by what your facade API offers, and so lose support for Telemere's advanced features like structured logging, [rich filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-filters), etc.
|
|
||||||
|
|
||||||
## 2. Telemere as a transitive dependency
|
## 2. Telemere as a transitive dependency
|
||||||
|
|
||||||
Include [Telemere](https://clojars.org/com.taoensso/telemere) in your **library's dependencies**. Your library (and users) will then have access to the full Telemere API.
|
Include [Telemere](https://clojars.org/com.taoensso/telemere) in your **library's dependencies**. Your library (and users) will then have access to the full Telemere API.
|
||||||
|
|
||||||
Telemere's [default config](./1-Getting-started#default-config) is sensible (with println-like console output), so many of library users won't need to configure or interact with Telemere at all.
|
Telemere's [default config](./1-Getting-started#default-config) is sensible (with println-like console output), so your users are unlikely to need to configure or interact with Telemere much unless they choose to.
|
||||||
|
|
||||||
The most common thing library users may want to do is **adjust the minimum level** of signals created by your library. And since your users might not be familiar with Telemere, I'd recommend including something like the following in a convenient place like your library's main API namespace:
|
The most common thing users may want to do is **adjust the minimum level** of signals created by your library. You can help make this as easy as possible by adding a util to your library:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(defn set-min-log-level!
|
(defn set-min-log-level!
|
||||||
|
|
@ -39,8 +37,43 @@ This way your users can easily disable, decrease, or increase signal output from
|
||||||
|
|
||||||
Include the (super lightweight) [Telemere facade API](https://clojars.org/com.taoensso/telemere-api) in your **library's dependencies**.
|
Include the (super lightweight) [Telemere facade API](https://clojars.org/com.taoensso/telemere-api) in your **library's dependencies**.
|
||||||
|
|
||||||
Your library will then be able to take advantage of Telemere **when Telemere is present**, or fall back to something like [tools.logging](https://github.com/clojure/tools.logging) otherwise.
|
Your library will then be able to emit structured logs/telemetry **when Telemere is present**, or fall back to something like [tools.logging](https://github.com/clojure/tools.logging) otherwise.
|
||||||
|
|
||||||
The main trade-off is that your signal calls will be more verbose.
|
The main trade-off is that your signal calls will be more verbose:
|
||||||
|
|
||||||
See [here](https://cljdoc.org/d/com.taoensso/telemere-api/CURRENT/api/taoensso.telemere.api) for an example and more info.
|
```clojure
|
||||||
|
(ns my-lib
|
||||||
|
(:require
|
||||||
|
[taoensso.telemere.api :as t] ; `com.taoensso/telemere-api` dependency
|
||||||
|
[clojure.tools.logging :as ctl] ; `org.clojure/tools.logging` dependency
|
||||||
|
))
|
||||||
|
|
||||||
|
(t/require-telemere-if-present) ; Just below `ns` form
|
||||||
|
|
||||||
|
;; Optional convenience for library users
|
||||||
|
(defn set-min-level!
|
||||||
|
"If it's present, sets Telemere's minimum level for <my-lib> namespaces.
|
||||||
|
This will affect all signals (logs) created by <my-lib>.
|
||||||
|
|
||||||
|
Possible minimum levels (from most->least verbose):
|
||||||
|
#{:trace :debug :info :warn :error :fatal :report}.
|
||||||
|
|
||||||
|
The default minimum level is `:warn`."
|
||||||
|
[min-level]
|
||||||
|
(t/if-telemere
|
||||||
|
(do (t/set-min-level! nil \"my-lib(.*)\" min-level) true)
|
||||||
|
false))
|
||||||
|
|
||||||
|
(defonce ^:private __set-default-min-level (set-min-level! :warn))
|
||||||
|
|
||||||
|
;; Creates Telemere signal if Telemere is present,
|
||||||
|
;; otherwise logs with tools.logging
|
||||||
|
(signal!
|
||||||
|
{:kind :log, :id :my-id, :level :warn,
|
||||||
|
:let [x :x]
|
||||||
|
:msg [\"Hello\" \"world\" x]
|
||||||
|
:data {:a :A :x x}
|
||||||
|
:fallback (ctl/warn (str \"Hello world\" x))})
|
||||||
|
```
|
||||||
|
|
||||||
|
See [here](https://cljdoc.org/d/com.taoensso/telemere-api/CURRENT/api/taoensso.telemere.api) for more info.
|
||||||
Loading…
Reference in a new issue