mirror of
https://github.com/taoensso/telemere.git
synced 2025-12-17 01:51:10 +00:00
[new] Add experimental facade API for lib authors, etc.
This commit is contained in:
parent
0f09b797ed
commit
ece51b2ef6
8 changed files with 264 additions and 80 deletions
15
projects/api/.gitignore
vendored
Normal file
15
projects/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
pom.xml*
|
||||||
|
.lein*
|
||||||
|
.nrepl-port
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
|
/lib/
|
||||||
|
/classes/
|
||||||
|
/target/
|
||||||
|
/checkouts/
|
||||||
|
/logs/
|
||||||
|
/.clj-kondo/.cache
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
44
projects/api/project.clj
Normal file
44
projects/api/project.clj
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
(defproject com.taoensso/telemere-api "1.0.0-SNAPSHOT"
|
||||||
|
:author "Peter Taoussanis <https://www.taoensso.com>"
|
||||||
|
:description "Minimal Telemere facade API for library authors, etc."
|
||||||
|
:url "https://www.taoensso.com/telemere"
|
||||||
|
|
||||||
|
:license
|
||||||
|
{:name "Eclipse Public License - v 1.0"
|
||||||
|
:url "https://www.eclipse.org/legal/epl-v10.html"}
|
||||||
|
|
||||||
|
:scm {:name "git" :url "https://github.com/taoensso/telemere"}
|
||||||
|
|
||||||
|
:dependencies []
|
||||||
|
:profiles
|
||||||
|
{:provided
|
||||||
|
{:dependencies
|
||||||
|
[[org.clojure/clojurescript "1.11.132"]
|
||||||
|
[org.clojure/clojure "1.11.4"]
|
||||||
|
[com.taoensso/telemere "1.0.0-SNAPSHOT"]]}
|
||||||
|
|
||||||
|
:dev
|
||||||
|
{:plugins
|
||||||
|
[[lein-pprint "1.3.2"]
|
||||||
|
[lein-ancient "0.7.0"]
|
||||||
|
[lein-cljsbuild "1.1.8"]]}}
|
||||||
|
|
||||||
|
:cljsbuild
|
||||||
|
{:test-commands {"node" ["node" "target/test.js"]}
|
||||||
|
:builds
|
||||||
|
[{:id :main
|
||||||
|
:source-paths ["src"]
|
||||||
|
:compiler
|
||||||
|
{:output-to "target/main.js"
|
||||||
|
:optimizations :advanced}}
|
||||||
|
|
||||||
|
{:id :test
|
||||||
|
:source-paths ["src" "test"]
|
||||||
|
:compiler
|
||||||
|
{:output-to "target/test.js"
|
||||||
|
:target :nodejs
|
||||||
|
:optimizations :simple}}]}
|
||||||
|
|
||||||
|
:aliases
|
||||||
|
{"build-once" ["do" ["clean"] ["cljsbuild" "once"]]
|
||||||
|
"deploy-lib" ["do" ["build-once"] ["deploy" "clojars"] ["install"]]})
|
||||||
153
projects/api/src/taoensso/telemere/api.cljc
Normal file
153
projects/api/src/taoensso/telemere/api.cljc
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
(ns taoensso.telemere.api
|
||||||
|
"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))})"
|
||||||
|
|
||||||
|
{:author "Peter Taoussanis (@ptaoussanis)"}
|
||||||
|
#?(:clj (:require [clojure.java.io :as jio])
|
||||||
|
:cljs (:require-macros [taoensso.telemere.api :refer [compile-if]])))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(require '[taoensso.telemere :as t] '[taoensso.encore :as enc])
|
||||||
|
(remove-ns 'taoensso.telemere.api)
|
||||||
|
(:api (enc/interns-overview)))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defmacro ^:private compile-if [test then else]
|
||||||
|
(if (try (eval test) (catch Throwable _ false)) then else)))
|
||||||
|
|
||||||
|
(def ^:private telemere-present?
|
||||||
|
"Is Telemere present (not necessarily loaded)?"
|
||||||
|
(compile-if (jio/resource "taoensso/telemere.cljc") true false))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defmacro if-telemere
|
||||||
|
"Evaluates to `then` form if Telemere is present when expanding macro.
|
||||||
|
Otherwise expands to `else` form."
|
||||||
|
([then ] (if telemere-present? then nil))
|
||||||
|
([then else] (if telemere-present? then else))))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defmacro require-telemere-if-present
|
||||||
|
"Experimental, subject to change.
|
||||||
|
Requires Telemere if it's present, otherwise noops.
|
||||||
|
Used in cooperation with `signal!`, etc.
|
||||||
|
|
||||||
|
For Cljs:
|
||||||
|
- MUST be placed at top of file, just below `ns` form
|
||||||
|
- Needs ClojureScript >= v1.9.293 (Oct 2016)"
|
||||||
|
[] (if-telemere `(require 'taoensso.telemere))))
|
||||||
|
|
||||||
|
(comment (require-telemere-if-present))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defmacro set-min-level!
|
||||||
|
"Expands to `taoensso.telemere/set-min-level!` call iff Telemere is present.
|
||||||
|
Otherwise expands to `nil` (noops)."
|
||||||
|
([ min-level] (if-telemere `(taoensso.telemere/set-min-level! ~min-level)))
|
||||||
|
([kind min-level] (if-telemere `(taoensso.telemere/set-min-level! ~kind ~min-level)))
|
||||||
|
([kind ns-filter min-level] (if-telemere `(taoensso.telemere/set-min-level! ~kind ~ns-filter ~min-level)))))
|
||||||
|
|
||||||
|
(comment (set-min-level! nil "my-ns(.*)" :warn))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defmacro signal!
|
||||||
|
"Experimental, subject to change.
|
||||||
|
Telemere facade API for library authors, etc.
|
||||||
|
|
||||||
|
Expands to `taoensso.telemere/signal!` call if Telemere is present.
|
||||||
|
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.
|
||||||
|
|
||||||
|
MUST be used with `require-telemere-if-present`, example:
|
||||||
|
|
||||||
|
(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!
|
||||||
|
|
||||||
|
(t/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))})
|
||||||
|
|
||||||
|
For more info, see:
|
||||||
|
- Telemere `signal!`, Ref. <https://www.taoensso.com/telemere/signal!>
|
||||||
|
- Telemere docs, Ref. <https://www.taoensso.com/telemere>"
|
||||||
|
|
||||||
|
{:arglists #_(taoensso.telemere.impl/signal-arglists :signal!) ; + fallback
|
||||||
|
'([{:as opts
|
||||||
|
:keys
|
||||||
|
[fallback, ; Unique to facade
|
||||||
|
#_defaults #_elide? #_allow? #_expansion-id, ; Undocumented
|
||||||
|
elidable? location #_location* inst uid middleware,
|
||||||
|
sample-rate kind ns id level when rate-limit,
|
||||||
|
ctx parent root trace?, do let data msg error run & kvs]}])}
|
||||||
|
|
||||||
|
[opts]
|
||||||
|
(if (map? opts)
|
||||||
|
(if telemere-present?
|
||||||
|
(do
|
||||||
|
(try
|
||||||
|
(require 'taoensso.telemere) ; For macro expansion
|
||||||
|
(catch Exception e
|
||||||
|
(throw
|
||||||
|
(ex-info "Failed to require `taoensso.telemere` - `(require-telemere-if-present)` call missing?"
|
||||||
|
{} e))))
|
||||||
|
|
||||||
|
(with-meta ; Keep callsite
|
||||||
|
`(taoensso.telemere/signal! ~(dissoc opts :fallback))
|
||||||
|
(meta &form)))
|
||||||
|
|
||||||
|
(let [fb-form (get opts :fallback)]
|
||||||
|
(if-let [run-form (get opts :run)]
|
||||||
|
`(let [run-result# ~run-form] ~fb-form run-result#)
|
||||||
|
(do fb-form))))
|
||||||
|
|
||||||
|
(throw
|
||||||
|
(ex-info "`signal!` expects map opts"
|
||||||
|
{:given {:value opts, :type (type opts)}})))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(macroexpand
|
||||||
|
'(signal!
|
||||||
|
{:kind :log, :id :my-id, :level :warn,
|
||||||
|
:let [x :x]
|
||||||
|
:msg ["Hello" "world" x]
|
||||||
|
:data {:a :A :x x}
|
||||||
|
:fallback (println (str "Hello world " x))})))
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
[[org.clojure/test.check "1.1.1"]
|
[[org.clojure/test.check "1.1.1"]
|
||||||
[org.clojure/tools.logging "1.3.0"]
|
[org.clojure/tools.logging "1.3.0"]
|
||||||
[org.slf4j/slf4j-api "2.0.16"]
|
[org.slf4j/slf4j-api "2.0.16"]
|
||||||
|
[com.taoensso/telemere-api "1.0.0-SNAPSHOT"]
|
||||||
[com.taoensso/slf4j-telemere "1.0.0-SNAPSHOT"]
|
[com.taoensso/slf4j-telemere "1.0.0-SNAPSHOT"]
|
||||||
#_[org.slf4j/slf4j-simple "2.0.16"]
|
#_[org.slf4j/slf4j-simple "2.0.16"]
|
||||||
#_[org.slf4j/slf4j-nop "2.0.16"]
|
#_[org.slf4j/slf4j-nop "2.0.16"]
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
:refer [signal! with-signal with-signals]
|
:refer [signal! with-signal with-signals]
|
||||||
:rename {signal! sig!, with-signal with-sig, with-signals with-sigs}]
|
:rename {signal! sig!, with-signal with-sig, with-signals with-sigs}]
|
||||||
|
|
||||||
|
[taoensso.telemere.api :as api]
|
||||||
[taoensso.telemere.utils :as utils]
|
[taoensso.telemere.utils :as utils]
|
||||||
[taoensso.telemere.timbre :as timbre]
|
[taoensso.telemere.timbre :as timbre]
|
||||||
#_[taoensso.telemere.tools-logging :as tools-logging]
|
#_[taoensso.telemere.tools-logging :as tools-logging]
|
||||||
|
|
@ -271,7 +272,9 @@
|
||||||
(update-in [:inst] enc/inst->udt)
|
(update-in [:inst] enc/inst->udt)
|
||||||
(update-in [:end-inst] enc/inst->udt))]
|
(update-in [:end-inst] enc/inst->udt))]
|
||||||
|
|
||||||
[(is (= sv1 (read-string (pr-str sv1))))])))])
|
[(is (= sv1 (read-string (pr-str sv1))))])))
|
||||||
|
|
||||||
|
(is (sm? (with-sig (api/signal! {:level :info})) {:level :info, :ns "taoensso.telemere-tests", :line :submap/some}) "API")])
|
||||||
|
|
||||||
(deftest _handlers
|
(deftest _handlers
|
||||||
;; Basic handler tests are in Encore
|
;; Basic handler tests are in Encore
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ See also [`msg-skip`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/tao
|
||||||
|
|
||||||
# How to use Telemere from a library?
|
# How to use Telemere from a library?
|
||||||
|
|
||||||
See section [9-Maintainers](./9-Maintainers).
|
See section [9-Authors](./9-Authors.md).
|
||||||
|
|
||||||
# How does Telemere compare to Mulog?
|
# How does Telemere compare to Mulog?
|
||||||
|
|
||||||
|
|
|
||||||
46
wiki/9-Authors.md
Normal file
46
wiki/9-Authors.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
Are you a library author/maintainer that's considering **using Telemere in your library**?
|
||||||
|
|
||||||
|
# Options
|
||||||
|
## 1. Consider a basic facade
|
||||||
|
|
||||||
|
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/).
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn set-min-log-level!
|
||||||
|
"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]
|
||||||
|
(tel/set-min-level! nil "my-lib-ns(.*)" min-level)
|
||||||
|
true)
|
||||||
|
|
||||||
|
(defonce ^:private __set-default-log-level (set-min-log-level! :warn))
|
||||||
|
```
|
||||||
|
|
||||||
|
This way your users can easily disable, decrease, or increase signal output from your library without even needing to touch Telemere or to be aware of its existence.
|
||||||
|
|
||||||
|
## 3. Telemere as an optional dependency
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
Are you a library maintainer that's considering **using Telemere in your library**?
|
|
||||||
|
|
||||||
See below for some considerations and advice-
|
|
||||||
|
|
||||||
# Consider a facade
|
|
||||||
|
|
||||||
Ask yourself the question: do you specifically *need/want* Telemere?
|
|
||||||
|
|
||||||
Many libraries only need very basic logging. In these cases it can be beneficial to do your logging through a facade like [tools.logging](https://github.com/clojure/tools.logging) or [SLF4J](https://www.slf4j.org/).
|
|
||||||
|
|
||||||
**Upside**: users can then easily choose and configure their **preferred backend** (including Telemere, which can easily [act as a backend](./3-Config#interop) for both tools.logging and SLF4J).
|
|
||||||
|
|
||||||
**Downside**: your logging features will necessarily be limited to the lowest-common-denominator features supported by your chosen facade (tools.logging or SLF4J, etc.). In particular, you'll be giving up support for Telemere's structured logging features and [rich filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-filters) (including filtering or setting minimum levels by namespaces).
|
|
||||||
|
|
||||||
# Consider API stability
|
|
||||||
|
|
||||||
Telemere is still currently in **beta** as of May 2024.
|
|
||||||
|
|
||||||
While the [signal creator](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) and [signal content](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) APIs should already be mostly stable, I would still **recommend against** using Telemere in any public libraries until after Telemere's **stable v1 release** (current ETA >= [August 2024](https://taoensso.com/roadmap)).
|
|
||||||
|
|
||||||
# Using Telemere in your library
|
|
||||||
|
|
||||||
If you **do** need/want support for Telemere's structured logging features and/or [rich filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-filters), then you've got a couple options-
|
|
||||||
|
|
||||||
## Telemere as a non-optional dependency
|
|
||||||
|
|
||||||
This is straight-forward: you include Telemere in your library's dependencies, and you can make use of Telemere's full API from your own library.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
(defn set-min-log-level!
|
|
||||||
"Sets minimum level of Telemere signals (logs) created by <my-library>.
|
|
||||||
Possible levels (from most to least verbose):
|
|
||||||
#{:trace :debug :info :warn :error :fatal :report}.
|
|
||||||
|
|
||||||
The default level is `:warn`."
|
|
||||||
[level]
|
|
||||||
(tel/set-min-level! nil "my-library.namespace" min-level)
|
|
||||||
(tel/set-min-level! nil "my-library.namespace.*" min-level)
|
|
||||||
nil)
|
|
||||||
|
|
||||||
(defonce ^:private __set-default-log-level (set-min-log-level! :warn))
|
|
||||||
```
|
|
||||||
|
|
||||||
This way your users can easily disable, decrease, or increase signal output from your library without even needing to touch Telemere or to be aware of its existence.
|
|
||||||
|
|
||||||
## Telemere as an optional dependency
|
|
||||||
|
|
||||||
I have a solution planned for this that I'm still testing. Will add more info prior to Telemere's [stable v1 release](https://www.taoensso.com/roadmap).
|
|
||||||
|
|
||||||
# Migrating from Timbre
|
|
||||||
|
|
||||||
Do you have a library that currently uses [Timbre](https://www.taoensso.com/timbre), and you're considering a change to Telemere?
|
|
||||||
|
|
||||||
## With a facade
|
|
||||||
|
|
||||||
First, I'd encourage you to [consider a facade](#consider-a-fascade).
|
|
||||||
|
|
||||||
Unless you specifically want features *only* available to Telemere/Timbre, using a facade would give your users the option to choose **whichever backend they prefer**.
|
|
||||||
|
|
||||||
Since both Telemere and Timbre support [tools.logging](https://github.com/clojure/tools.logging) and [SLF4J](https://www.slf4j.org/), either one of those would be a reasonable choice.
|
|
||||||
|
|
||||||
(Though note that Timbre's current SLF4J support is a little fragile. Timbre v7 [will introduce](https://github.com/taoensso/roadmap/issues/11) improved native SLF4J support, so you may want to wait for that if you are considering SLF4J).
|
|
||||||
|
|
||||||
Example migration steps:
|
|
||||||
|
|
||||||
1. Migrate your library's Timbre logging calls to equivalent [`tools.logging`](https://github.com/clojure/tools.logging) calls.
|
|
||||||
2. Remove your library's Timbre dependency.
|
|
||||||
3. Advise your users about the dropped dependency, and tell them that they'll now need to opt-in to [use Telemere](https://github.com/taoensso/telemere/wiki/3-Config#toolslogging), [use Timbre](https://taoensso.github.io/timbre/taoensso.timbre.tools.logging.html#var-use-timbre), or use some other logging backend for tools.logging that they prefer.
|
|
||||||
|
|
||||||
## Without a facade
|
|
||||||
|
|
||||||
In this case you'll need to decide if you want to use Telemere as an [optional](#telemere-as-an-optional-dependency) or [non-optional](#telemere-as-a-non-optional-dependency) dependency.
|
|
||||||
|
|
||||||
Will add more info prior to Telemere's [stable v1 release](https://www.taoensso.com/roadmap).
|
|
||||||
Loading…
Reference in a new issue