diff --git a/README.md b/README.md index a7a827a..3fed4a4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,9 @@ **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**. -Friendly enough for complete beginners, but flexible enough for the most complex and performance-sensitive real-world projects. +It's implemented in **pure Clojure/Script**, and **just works out the box** with none of the mess of Java logging. + +It's friendly enough for complete beginners, but flexible enough for the most complex and performance-sensitive real-world projects. 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. @@ -85,7 +87,7 @@ 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. @@ -289,7 +291,7 @@ Telemere is optimized for *real-world* performance. This means **prioritizing fl 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**. +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. 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/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..aab638e 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: diff --git a/wiki/5-Migrating.md b/wiki/5-Migrating.md index 17a6cf4..a61b8e5 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#benchmarks) +- 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,7 +63,7 @@ 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 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: + +```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 . -The main trade-off is that your signal calls will be more verbose. + 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 an example and more info. \ No newline at end of file +See [here](https://cljdoc.org/d/com.taoensso/telemere-api/CURRENT/api/taoensso.telemere.api) for more info. \ No newline at end of file