[doc] Misc improvements

This commit is contained in:
Peter Taoussanis 2024-08-28 22:22:20 +02:00
parent f9564b2fc5
commit e4a0a41a1b
13 changed files with 142 additions and 127 deletions

113
README.md
View file

@ -5,15 +5,20 @@
### 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
@ -22,7 +27,21 @@ See [here](../../wiki/1-Getting-started) for **full introduction**.
[![Main tests][Main tests SVG]][Main 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
@ -39,7 +58,7 @@ See [here][GitHub releases] for earlier releases.
(t/log! {:level :info, :data {...}} "Hello again!")
(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
(t/trace! {:id ::my-id :data {...}}
(do-some-work))
@ -56,7 +75,7 @@ See [here][GitHub releases] for earlier releases.
;; Getting fancy (all costs are conditional!)
(t/log!
{:level :debug
:sample-rate (my-dynamic-sample-rate)
:sample-rate 0.75 ; 75% sampling (noop 25% of the time)
:when (my-conditional)
:rate-limit {"1 per sec" [1 1000]
"5 per min" [5 60000]}
@ -85,13 +104,13 @@ See [here][GitHub releases] for earlier releases.
### 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).
- Extensive set of [handlers](../../wiki/4-Handlers#included-handlers) included out-the-box.
### 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.
- 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/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
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: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 to **vote on future handlers**:
@ -266,35 +300,6 @@ See [here](../../wiki/8-Community) for community resources.
- 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)
## 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
You can [help support][sponsor] continued work on this project, thank you!! 🙏

View file

@ -35,7 +35,7 @@
;; Getting fancy (all costs are conditional!)
(t/log!
{:level :debug
:sample-rate (my-dynamic-sample-rate)
:sample-rate 0.75 ; 75% sampling (noop 25% of the time)
:when (my-conditional)
:rate-limit {"1 per sec" [1 1000]
"5 per min" [5 60000]}

View file

@ -2,35 +2,7 @@
"Experimental, subject to change.
Minimal Telemere facade API for library authors, etc.
Allows library code to use Telemere if it's present, or fall back to
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))})"
something like tools.logging otherwise."
{:author "Peter Taoussanis (@ptaoussanis)"}
#?(:clj (:require [clojure.java.io :as jio])
:cljs (:require-macros [taoensso.telemere.api :refer [compile-if]])))
@ -87,7 +59,7 @@
Otherwise expands to arbitrary `fallback` opt form.
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:

View file

@ -27,7 +27,8 @@ Tips:
- Test using `with-signal`: (with-signal (catch->error! ...)).
- 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!`.

View file

@ -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
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:
(spy! (+ 1 2)) ; %> {:kind :trace, :level :info, :run-form '(+ 1 2),

View file

@ -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
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:
(trace! (+ 1 2)) ; %> {:kind :trace, :level :info, :run-form '(+ 1 2),

View file

@ -758,7 +758,7 @@
#?(:clj
(defmacro signal-allowed?
"Used only for interop (SLF4J, `tools.logging`, etc.)."
"Used only for interop (tools.logging, SLF4J, etc.)."
{:arglists (signal-arglists :signal!)}
[opts]
(let [{:keys [#_expansion-id #_location elide? allow?]}

View file

@ -1,5 +1,5 @@
(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.
Naming conventions:
@ -41,7 +41,7 @@
(get-logger [_ logger-name] (TelemereLogger. (str logger-name))))
(defn tools-logging->telemere!
"Configures `tools.logging` to use Telemere as its logging
"Configures tools.logging to use Telemere as its logging
implementation (backend).
Called automatically if one of the following is \"true\":
@ -53,13 +53,13 @@
{:kind :event
:level :debug ; < :info since runs on init
: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*
(fn [_] (TelemereLoggerFactory.))))
(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)."
[]
(when-let [lf clojure.tools.logging/*logger-factory*]
@ -73,7 +73,7 @@
(let [sending? (tools-logging->telemere?)
receiving?
(and sending?
(impl/test-interop! "`tools.logging` -> Telemere"
(impl/test-interop! "tools.logging -> Telemere"
#(clojure.tools.logging/info %)))]
{:present? true

View file

@ -657,7 +657,7 @@
#?(:clj
(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? (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?}))

View file

@ -25,11 +25,11 @@ See section [4-Handlers](./4-Handlers).
## 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:
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.
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
[`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
[org.slf4j/slf4j-api "x.y.z"] ; >= 2.0.0 only!
[com.taoensso/telemere-slf4j "x.y.z"]
```
1. Ensure that you have the SLF4J [dependency](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) ( v2+ **only**), and
2. Ensure that you have the Telemere SLF4J backend [dependency](https://clojars.org/com.taoensso/telemere-slf4j)
> 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` is on your classpath AND no other SLF4J backends are, SLF4J will direct all its logging calls to Telemere.
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.
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}}
```
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:
@ -96,7 +94,7 @@ This allows output to go (via configured exporters) to a wide variety of targets
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).
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.
@ -117,7 +115,7 @@ Telemere can easily incorporate Tufte performance data in its signals, just like
Telemere and Tufte work great together:
- 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
@ -129,4 +127,4 @@ The [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/tao
```clojure
(t/catch->error! <form-with-truss-assertion/s>)
```
```

View file

@ -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:
- Better support for [structured logging](./1-Getting-started#data-types-and-structures)
- Better [performance](https://github.com/taoensso/telemere#benchmarks)
- Better [documentation](https://github.com/taoensso/telemere#documentation)
- Better [included handlers](./4-Handlers##included-handlers)
- Much better [performance](https://github.com/taoensso/telemere#performance)
- Much better [documentation](https://github.com/taoensso/telemere#documentation)
- 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
- Better [included handlers](./4-Handlers##included-handlers)
- 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).
@ -63,8 +63,8 @@ If for any reason your tests are unsuccessful, please don't feel pressured to mi
# From tools.logging
This is easy, see [here](./3-Config#clojuretoolslogging).
This is easy, see [here](./3-Config#toolslogging).
# From Java logging
This is easy, see [here](./3-Config#java-logging).
This is easy, see [here](./3-Config#java-logging).

View file

@ -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.
**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.
@ -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.
Yes, this shouldn't be a problem.
**Yes**, this shouldn't be a problem.
# Does Telemere work with Babashka?
> [Babashka](https://github.com/babashka/babashka) is a native Clojure interpreter for scripting with fast startup.
Not currently, though support should be possible with a little work. The current bottleneck is a dependency on [Encore](https://github.com/taoensso/encore), though that could actually be removed (also offering benefits re: library size).
**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.
@ -161,4 +161,4 @@ Ultimately I wrote Telemere because:
# Other questions?
Please [open a Github issue](https://github.com/taoensso/telemere/issues) or ping on Telemere's [Slack channel](https://www.taoensso.com/telemere/slack). I'll regularly update the FAQ to add common questions. - [Peter](https://www.taoensso.com)
Please [open a Github issue](https://github.com/taoensso/telemere/issues) or ping on Telemere's [Slack channel](https://www.taoensso.com/telemere/slack). I'll regularly update the FAQ to add common questions. - [Peter](https://www.taoensso.com)

View file

@ -1,21 +1,19 @@
Are you a library author/maintainer that's considering **using Telemere in your library**?
# 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.
**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.
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)).
## 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.
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
(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**.
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.