diff --git a/README.md b/README.md index a7a827a..409927d 100644 --- a/README.md +++ b/README.md @@ -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. + + +## 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!! 🙏 diff --git a/examples.cljc b/examples.cljc index f2a25bf..251d56b 100644 --- a/examples.cljc +++ b/examples.cljc @@ -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]} diff --git a/projects/api/src/taoensso/telemere/api.cljc b/projects/api/src/taoensso/telemere/api.cljc index cc171ff..2e0467d 100644 --- a/projects/api/src/taoensso/telemere/api.cljc +++ b/projects/api/src/taoensso/telemere/api.cljc @@ -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 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: diff --git a/projects/main/resources/signal-docstrings/catch-to-error!.txt b/projects/main/resources/signal-docstrings/catch-to-error!.txt index 12d9072..c3ac340 100644 --- a/projects/main/resources/signal-docstrings/catch-to-error!.txt +++ b/projects/main/resources/signal-docstrings/catch-to-error!.txt @@ -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!`. diff --git a/projects/main/resources/signal-docstrings/spy!.txt b/projects/main/resources/signal-docstrings/spy!.txt index 6eaa4f4..044d61e 100644 --- a/projects/main/resources/signal-docstrings/spy!.txt +++ b/projects/main/resources/signal-docstrings/spy!.txt @@ -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), diff --git a/projects/main/resources/signal-docstrings/trace!.txt b/projects/main/resources/signal-docstrings/trace!.txt index eae6a45..c858c28 100644 --- a/projects/main/resources/signal-docstrings/trace!.txt +++ b/projects/main/resources/signal-docstrings/trace!.txt @@ -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), diff --git a/projects/main/src/taoensso/telemere/impl.cljc b/projects/main/src/taoensso/telemere/impl.cljc index b653324..73f28cd 100644 --- a/projects/main/src/taoensso/telemere/impl.cljc +++ b/projects/main/src/taoensso/telemere/impl.cljc @@ -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?]} diff --git a/projects/main/src/taoensso/telemere/tools_logging.clj b/projects/main/src/taoensso/telemere/tools_logging.clj index 04b1e0f..26d6f14 100644 --- a/projects/main/src/taoensso/telemere/tools_logging.clj +++ b/projects/main/src/taoensso/telemere/tools_logging.clj @@ -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 diff --git a/projects/main/test/taoensso/telemere_tests.cljc b/projects/main/test/taoensso/telemere_tests.cljc index 369b24c..62b826f 100644 --- a/projects/main/test/taoensso/telemere_tests.cljc +++ b/projects/main/test/taoensso/telemere_tests.cljc @@ -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?})) diff --git a/wiki/3-Config.md b/wiki/3-Config.md index c4fadd0..01beb46 100644 --- a/wiki/3-Config.md +++ b/wiki/3-Config.md @@ -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! ) -``` \ No newline at end of file +``` diff --git a/wiki/5-Migrating.md b/wiki/5-Migrating.md index 17a6cf4..4bc025c 100644 --- a/wiki/5-Migrating.md +++ b/wiki/5-Migrating.md @@ -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). \ No newline at end of file +This is easy, see [here](./3-Config#java-logging). diff --git a/wiki/6-FAQ.md b/wiki/6-FAQ.md index 159d32b..bbe661f 100644 --- a/wiki/6-FAQ.md +++ b/wiki/6-FAQ.md @@ -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) \ No newline at end of file +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) diff --git a/wiki/9-Authors.md b/wiki/9-Authors.md index 3565ff6..4bba858 100644 --- a/wiki/9-Authors.md +++ b/wiki/9-Authors.md @@ -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. \ No newline at end of file +```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 namespaces. + This will affect all signals (logs) created by . + + 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. \ No newline at end of file