[fix] Fix broken AOT support, add AOT tests

Thanks to @AdamFrey for reporting this issue!
Ref. <https://clojurians.slack.com/archives/C06ALA6EEUA/p1713805333272469>

Previously:

  Attempting to run AOT'd code using Telemere would result in errors like:
  "Attempting to call unbound fn: #'taoensso.telemere.handlers.open-telemetry/handler:open-telemetry-logger"

The approach I was using of conditionally requiring namespaces and then aliasing vars seems to be inherently
fragile under AOT, and was leading to the remote source var being unbound.

With this commit I've now switched to a simpler approach - where we conditionally require namespaces *without*
the need for any aliasing.
This commit is contained in:
Peter Taoussanis 2024-04-23 11:25:15 +02:00
parent b98e492071
commit ffea1a30ed
12 changed files with 186 additions and 153 deletions

View file

@ -29,10 +29,12 @@
[[org.clojure/clojure "1.11.2"] [[org.clojure/clojure "1.11.2"]
[com.github.clj-easy/graal-build-time "1.0.5"]]} [com.github.clj-easy/graal-build-time "1.0.5"]]}
:test {:aot [taoensso.telemere-tests]}
:dev :dev
{:jvm-opts {:jvm-opts
["-server" ["-server"
"-Dtaoensso.elide-deprecated=true" "-Dtaoensso.elide-deprecated=true"
"-Dtaoensso.telemere.auto-handlers=false"
"-Dclojure.tools.logging-to-telemere?=true"] "-Dclojure.tools.logging-to-telemere?=true"]
:global-vars :global-vars

View file

@ -1,6 +1,6 @@
(ns ^:no-doc taoensso.telemere.slf4j (ns taoensso.telemere.slf4j
"Private ns, implementation detail. "Intake support for SLF4J -> Telemere.
Intake support: SLF4J -> Telemere. Telemere will attempt to load this ns automatically when possible.
To use Telemere as your SLF4J backend/provider, just include the To use Telemere as your SLF4J backend/provider, just include the
`com.taoensso/slf4j-telemere` dependency on your classpath. `com.taoensso/slf4j-telemere` dependency on your classpath.
@ -18,7 +18,11 @@
(:require (:require
[taoensso.encore :as enc :refer [have have?]] [taoensso.encore :as enc :refer [have have?]]
[taoensso.telemere.impl :as impl])) [taoensso.telemere.impl :as impl])
(:import
[org.slf4j Logger]
[com.taoensso.telemere.slf4j TelemereLogger]))
;;;; Utils ;;;; Utils
@ -40,10 +44,10 @@
(comment (enc/qb 1e6 (sig-level org.slf4j.event.Level/INFO))) ; 36.47 (comment (enc/qb 1e6 (sig-level org.slf4j.event.Level/INFO))) ; 36.47
(defn get-marker "Private util for tests, etc." (defn- get-marker "Private util for tests, etc."
^org.slf4j.Marker [n] (org.slf4j.MarkerFactory/getMarker n)) ^org.slf4j.Marker [n] (org.slf4j.MarkerFactory/getMarker n))
(defn est-marker! (defn- est-marker!
"Private util for tests, etc. "Private util for tests, etc.
Globally establishes (compound) `org.slf4j.Marker` with name `n` and mutates it Globally establishes (compound) `org.slf4j.Marker` with name `n` and mutates it
(all occurences!) to have exactly the given references. Returns the (compound) marker." (all occurences!) to have exactly the given references. Returns the (compound) marker."
@ -55,7 +59,7 @@
(comment [(est-marker! "a1" "a2") (get-marker "a1") (= (get-marker "a1") (get-marker "a1"))]) (comment [(est-marker! "a1" "a2") (get-marker "a1") (= (get-marker "a1") (get-marker "a1"))])
(def marker-names (def ^:private marker-names
"Returns #{<MarkerName>}. Cached => assumes markers NOT modified after creation." "Returns #{<MarkerName>}. Cached => assumes markers NOT modified after creation."
;; We use `BasicMarkerFactory` so: ;; We use `BasicMarkerFactory` so:
;; 1. Our markers are just labels (no other content besides their name). ;; 1. Our markers are just labels (no other content besides their name).
@ -95,7 +99,7 @@
;;;; Intake fns (called by `TelemereLogger`) ;;;; Intake fns (called by `TelemereLogger`)
(defn allowed? (defn- allowed?
"Private, don't use. "Private, don't use.
Called by `com.taoensso.telemere.slf4j.TelemereLogger`." Called by `com.taoensso.telemere.slf4j.TelemereLogger`."
[^org.slf4j.event.Level level] [^org.slf4j.event.Level level]
@ -134,7 +138,7 @@
:slf4j/kvs kvs)}) :slf4j/kvs kvs)})
nil) nil)
(defn log! (defn- log!
"Private, don't use. "Private, don't use.
Called by `com.taoensso.telemere.slf4j.TelemereLogger`." Called by `com.taoensso.telemere.slf4j.TelemereLogger`."
@ -172,15 +176,27 @@
(org.slf4j.MDC/getCopyOfContextMap) (org.slf4j.MDC/getCopyOfContextMap)
(org.slf4j.MDC/clear))) (org.slf4j.MDC/clear)))
(impl/add-intake-check! :slf4j ;;;;
(fn []
(let [^org.slf4j.Logger sl
(org.slf4j.LoggerFactory/getLogger "IntakeTestTelemereLogger")
sending? (instance? com.taoensso.telemere.slf4j.TelemereLogger sl)
receiving?
(and sending?
(impl/test-intake! "SLF4J -> Telemere" #(.info sl %)))]
{:present? true (defn check-intake
:sending->telemere? sending? "Returns {:keys [present? sending->telemere? telemere-receiving?]}."
:telemere-receiving? receiving?}))) []
(let [^org.slf4j.Logger sl
(org.slf4j.LoggerFactory/getLogger "IntakeTestTelemereLogger")
sending? (instance? com.taoensso.telemere.slf4j.TelemereLogger sl)
receiving?
(and sending?
(impl/test-intake! "SLF4J -> Telemere" #(.info sl %)))]
{:present? true
:sending->telemere? sending?
:telemere-receiving? receiving?}))
(impl/add-intake-check! :slf4j check-intake)
(impl/on-init
(impl/signal!
{:kind :event
:level :info
:id :taoensso.telemere/slf4j->telemere!
:msg "Enabling intake: SLF4J -> Telemere"}))

View file

@ -375,25 +375,6 @@
streams/streams->telemere! streams/streams->telemere!
streams/streams->reset!)) streams/streams->reset!))
#?(:clj
(enc/compile-when
(do (require '[taoensso.telemere.tools-logging :as ttl]) true)
(enc/defalias ttl/tools-logging->telemere!) ; Incl. `get-env` docs
(when (enc/get-env {:as :bool} :clojure.tools.logging-to-telemere?)
(ttl/tools-logging->telemere!))))
#?(:clj
(enc/compile-when
(and org.slf4j.Logger com.taoensso.telemere.slf4j.TelemereLogger)
(impl/signal!
{:kind :event
:level :info
:id :taoensso.telemere/slf4j->telemere!
:msg "Enabling intake: SLF4J -> Telemere"})
(require '[taoensso.telemere.slf4j :as slf4j])))
(comment (check-intakes)) (comment (check-intakes))
;;;; Handlers ;;;; Handlers
@ -403,20 +384,6 @@
#?(:cljs handlers:console/handler:console-raw) #?(:cljs handlers:console/handler:console-raw)
#?(:clj handlers:file/handler:file)) #?(:clj handlers:file/handler:file))
#?(:clj
(enc/compile-when
(do (require '[taoensso.telemere.handlers.open-telemetry :as handlers:open-tel]) true)
(enc/defalias handlers:open-tel/handler:open-telemetry-logger)))
(defonce ^:no-doc __add-default-handlers
(do
(add-handler! :default/console (handler:console))
#?(:clj
(enc/compile-when handler:open-telemetry-logger
(when-let [handler (enc/catching (handler:open-telemetry-logger))]
(add-handler! :default/open-telemetry-logger handler))))
nil))
;;;; Flow benchmarks ;;;; Flow benchmarks
(comment (comment
@ -450,6 +417,16 @@
;;;; ;;;;
(impl/on-init
(when impl/auto-handlers?
(add-handler! :default/console (handler:console)))
#?(:clj (enc/catching (require '[taoensso.telemere.tools-logging])))
#?(:clj (enc/catching (require '[taoensso.telemere.slf4j])))
#?(:clj (enc/catching (require '[taoensso.telemere.handlers.open-telemetry]))))
;;;;
(comment (comment
(with-handler :hid1 (handlers/console-handler) {} (log! "Message")) (with-handler :hid1 (handlers/console-handler) {} (log! "Message"))
@ -464,5 +441,3 @@
(do (let [hf (handlers/file-handler)] (hf sig) (hf))) (do (let [hf (handlers/file-handler)] (hf sig) (hf)))
(do (let [hf (handlers/console-handler)] (hf sig) (hf))) (do (let [hf (handlers/console-handler)] (hf sig) (hf)))
#?(:cljs (let [hf (handlers/raw-console-handler)] (hf sig) (hf))))) #?(:cljs (let [hf (handlers/raw-console-handler)] (hf sig) (hf)))))
;;;;

View file

@ -1,6 +1,6 @@
(ns ^:no-doc taoensso.telemere.handlers.open-telemetry (ns taoensso.telemere.handlers.open-telemetry
"Private ns, implementation detail. "Core OpenTelemetry handler and utils.
Core OpenTelemetry handlers. Telemere will attempt to load this ns automatically when possible.
Needs `OpenTelemetry Java`, Needs `OpenTelemetry Java`,
Ref. <https://github.com/open-telemetry/opentelemetry-java>." Ref. <https://github.com/open-telemetry/opentelemetry-java>."
@ -8,7 +8,9 @@
(:require (:require
[clojure.string :as str] [clojure.string :as str]
[taoensso.encore :as enc :refer [have have?]] [taoensso.encore :as enc :refer [have have?]]
[taoensso.telemere.utils :as utils]) [taoensso.telemere.utils :as utils]
[taoensso.telemere.impl :as impl]
[taoensso.telemere :as tel])
(:import (:import
[io.opentelemetry.api.logs LoggerProvider Severity] [io.opentelemetry.api.logs LoggerProvider Severity]
@ -21,7 +23,7 @@
;;;; Implementation ;;;; Implementation
(defn level->severity (defn- level->severity
^Severity [level] ^Severity [level]
(case level (case level
:trace Severity/TRACE :trace Severity/TRACE
@ -33,7 +35,7 @@
:report Severity/INFO4 :report Severity/INFO4
Severity/UNDEFINED_SEVERITY_NUMBER)) Severity/UNDEFINED_SEVERITY_NUMBER))
(def ^String attr-name (def ^:private ^String attr-name
"Returns cached OpenTelemetry-style name: `:foo/bar-baz` -> \"foo_bar_baz\", etc. "Returns cached OpenTelemetry-style name: `:foo/bar-baz` -> \"foo_bar_baz\", etc.
Ref. <https://opentelemetry.io/docs/specs/semconv/general/attribute-naming/>." Ref. <https://opentelemetry.io/docs/specs/semconv/general/attribute-naming/>."
(enc/fmemoize (enc/fmemoize
@ -49,7 +51,7 @@
(comment (enc/qb 1e6 (attr-name :x1.x2/x3-x4 :Foo/Bar-BAZ))) ; 63.6 (comment (enc/qb 1e6 (attr-name :x1.x2/x3-x4 :Foo/Bar-BAZ))) ; 63.6
;; AttributeTypes: String, Long, Double, Boolean, and arrays ;; AttributeTypes: String, Long, Double, Boolean, and arrays
(defprotocol IAttr+ (attr+ [_aval akey builder])) (defprotocol IAttr+ (^:private attr+ [_aval akey builder]))
(extend-protocol IAttr+ (extend-protocol IAttr+
nil (attr+ [v k ^AttributesBuilder b] (.put b (attr-name k) "nil")) ; Like pr-edn* nil (attr+ [v k ^AttributesBuilder b] (.put b (attr-name k) "nil")) ; Like pr-edn*
Boolean (attr+ [v k ^AttributesBuilder b] (.put b (attr-name k) v)) Boolean (attr+ [v k ^AttributesBuilder b] (.put b (attr-name k) v))
@ -78,7 +80,7 @@
Object (attr+ [v k ^AttributesBuilder b] (.put b (attr-name k) (enc/pr-edn* v)))) Object (attr+ [v k ^AttributesBuilder b] (.put b (attr-name k) (enc/pr-edn* v))))
(defn as-attrs (defn- as-attrs
"Returns `io.opentelemetry.api.common.Attributes` for given map." "Returns `io.opentelemetry.api.common.Attributes` for given map."
^Attributes [m] ^Attributes [m]
(if (empty? m) (if (empty? m)
@ -89,7 +91,7 @@
(comment (str (as-attrs {:s "s", :kw :foo/bar, :long 5, :double 5.0, :longs [5 5 5] :nil nil}))) (comment (str (as-attrs {:s "s", :kw :foo/bar, :long 5, :double 5.0, :longs [5 5 5] :nil nil})))
(defn merge-prefix-map (defn- merge-prefix-map
"Merges prefixed `from` into `to`." "Merges prefixed `from` into `to`."
[to prefix from] [to prefix from]
(enc/cond (enc/cond
@ -103,7 +105,7 @@
(comment (merge-prefix-map {} "data" {:a/b1 "v1" :a/b2 "v2" :nil nil})) (comment (merge-prefix-map {} "data" {:a/b1 "v1" :a/b2 "v2" :nil nil}))
(defn signal->attrs-map (defn- signal->attrs-map
"Returns attributes map for given signal, "Returns attributes map for given signal,
Ref. <https://opentelemetry.io/docs/specs/otel/logs/data-model/>." Ref. <https://opentelemetry.io/docs/specs/otel/logs/data-model/>."
[attrs-key signal] [attrs-key signal]
@ -181,7 +183,7 @@
;;;; Handler ;;;; Handler
(defn ^:public handler:open-telemetry-logger (defn handler:open-telemetry-logger
"Experimental, subject to change!! Feedback very welcome! "Experimental, subject to change!! Feedback very welcome!
Returns a (fn handler [signal]) that: Returns a (fn handler [signal]) that:
@ -216,3 +218,10 @@
(.setSeverity severity) (.setSeverity severity)
(.setBody msg) (.setBody msg)
(.setAllAttributes attrs))))))))) (.setAllAttributes attrs)))))))))
;;;;
(impl/on-init
(when impl/auto-handlers?
(when-let [handler (enc/catching (handler:open-telemetry-logger))]
(tel/add-handler! :default/open-telemetry-logger handler))))

View file

@ -21,8 +21,18 @@
;;;; Utils ;;;; Utils
(def auto-handlers? (enc/get-env {:as :bool, :default true} :taoensso.telemere/auto-handlers))
#?(:clj (defmacro threaded [& body] `(let [t# (Thread. (fn [] ~@body))] (.start t#) t#))) #?(:clj (defmacro threaded [& body] `(let [t# (Thread. (fn [] ~@body))] (.start t#) t#)))
#?(:clj
(defmacro on-init [& body]
(let [sym (with-meta '__on-init {:private true})
compiling? (if (:ns &env) false `*compile-files*)]
`(defonce ~sym (when-not ~compiling? ~@body nil)))))
(comment (macroexpand-1 '(on-init (println "foo"))))
;;;; Config ;;;; Config
#?(:clj #?(:clj

View file

@ -1,23 +1,26 @@
(ns ^:no-doc taoensso.telemere.streams (ns taoensso.telemere.streams
"Private ns, implementation detail. "Intake support for standard stream/s -> Telemere."
Intake support: standard stream/s -> Telemere."
(:refer-clojure :exclude [binding]) (:refer-clojure :exclude [binding])
(:require (:require
[taoensso.encore :as enc :refer [binding have have?]] [taoensso.encore :as enc :refer [binding have have?]]
[taoensso.telemere.impl :as impl])) [taoensso.telemere.impl :as impl]))
(enc/defonce orig-*out* "Original `*out*` on ns load" *out*) (enc/defonce ^:private orig-*out* "Original `*out*` on ns load" *out*)
(enc/defonce orig-*err* "Original `*err*` on ns load" *err*) (enc/defonce ^:private orig-*err* "Original `*err*` on ns load" *err*)
(enc/defonce ^:dynamic prev-*out* "Previous `*out*` (prior to any Telemere binds)" nil) (enc/defonce ^:no-doc ^:dynamic prev-*out* "Previous `*out*` (prior to any Telemere binds)" nil)
(enc/defonce ^:dynamic prev-*err* "Previous `*err*` (prior to any Telemere binds)" nil) (enc/defonce ^:no-doc ^:dynamic prev-*err* "Previous `*err*` (prior to any Telemere binds)" nil)
(def ^:private ^:const default-out-opts {:kind :system/out, :level :info}) (def ^:private ^:const default-out-opts {:kind :system/out, :level :info})
(def ^:private ^:const default-err-opts {:kind :system/err, :level :error}) (def ^:private ^:const default-err-opts {:kind :system/err, :level :error})
(defn osw ^java.io.OutputStreamWriter [x] (java.io.OutputStreamWriter. x)) (defn ^:no-doc osw
"Private, don't use."
^java.io.OutputStreamWriter [x]
(java.io.OutputStreamWriter. x))
(defn telemere-print-stream (defn ^:no-doc telemere-print-stream
"Returns a `java.io.PrintStream` that will flush to Telemere signals with given opts." "Private, don't use.
Returns a `java.io.PrintStream` that will flush to Telemere signals with given opts."
^java.io.PrintStream [{:as sig-opts :keys [kind level id]}] ^java.io.PrintStream [{:as sig-opts :keys [kind level id]}]
(let [baos (let [baos
(proxy [java.io.ByteArrayOutputStream] [] (proxy [java.io.ByteArrayOutputStream] []
@ -42,6 +45,8 @@
(java.io.PrintStream. baos true ; Auto flush (java.io.PrintStream. baos true ; Auto flush
java.nio.charset.StandardCharsets/UTF_8))) java.nio.charset.StandardCharsets/UTF_8)))
;;;;
(defmacro ^:public with-out->telemere (defmacro ^:public with-out->telemere
"Executes form with `*out*` bound to flush to Telemere signals with given opts." "Executes form with `*out*` bound to flush to Telemere signals with given opts."
([ form] `(with-out->telemere nil ~form)) ([ form] `(with-out->telemere nil ~form))
@ -143,14 +148,21 @@
(streams->telemere! {}) (streams->telemere! {})
(streams->reset!)) (streams->reset!))
(impl/add-intake-check! :system/out ;;;;
(fn []
(let [sending? (boolean @orig-out_)
receiving? (and sending? (impl/test-intake! "`System/out` -> Telemere" #(.println System/out %)))]
{:sending->telemere? sending?, :telemere-receiving? receiving?})))
(impl/add-intake-check! :system/err (defn check-out-intake
(fn [] "Returns {:keys [sending->telemere? telemere-receiving?]}."
(let [sending? (boolean @orig-err_) []
receiving? (and sending? (impl/test-intake! "`System/err` -> Telemere" #(.println System/err %)))] (let [sending? (boolean @orig-out_)
{:sending->telemere? sending?, :telemere-receiving? receiving?}))) receiving? (and sending? (impl/test-intake! "`System/out` -> Telemere" #(.println System/out %)))]
{:sending->telemere? sending?, :telemere-receiving? receiving?}))
(defn check-err-intake
"Returns {:keys [sending->telemere? telemere-receiving?]}."
[]
(let [sending? (boolean @orig-err_)
receiving? (and sending? (impl/test-intake! "`System/err` -> Telemere" #(.println System/err %)))]
{:sending->telemere? sending?, :telemere-receiving? receiving?}))
(impl/add-intake-check! :system/out check-out-intake)
(impl/add-intake-check! :system/err check-err-intake)

View file

@ -1,6 +1,6 @@
(ns ^:no-doc taoensso.telemere.tools-logging (ns taoensso.telemere.tools-logging
"Private ns, implementation detail. "Intake support for `clojure.tools.logging` -> Telemere.
Intake support: `clojure.tools.logging` -> Telemere." Telemere will attempt to load this ns automatically when possible."
(:require (:require
[taoensso.encore :as enc :refer [have have?]] [taoensso.encore :as enc :refer [have have?]]
[taoensso.telemere.impl :as impl] [taoensso.telemere.impl :as impl]
@ -36,7 +36,7 @@
(name [_ ] "taoensso.telemere") (name [_ ] "taoensso.telemere")
(get-logger [_ logger-ns] (TelemereLogger. (str logger-ns)))) (get-logger [_ logger-ns] (TelemereLogger. (str logger-ns))))
(defn ^:public tools-logging->telemere! (defn tools-logging->telemere!
"Configures `clojure.tools.logging` to use Telemere as its logging implementation. "Configures `clojure.tools.logging` to use Telemere as its logging implementation.
Called automatically if the following is true: Called automatically if the following is true:
@ -53,19 +53,30 @@
(alter-var-root #'clojure.tools.logging/*logger-factory* (alter-var-root #'clojure.tools.logging/*logger-factory*
(fn [_] (TelemereLoggerFactory.)))) (fn [_] (TelemereLoggerFactory.))))
(defn tools-logging-factory [] (TelemereLoggerFactory.)) (defn tools-logging->telemere?
(defn tools-logging->telemere? [] "Returns true iff `clojure.tools.logging` is configured to use Telemere
as its logging implementation."
[]
(when-let [lf clojure.tools.logging/*logger-factory*] (when-let [lf clojure.tools.logging/*logger-factory*]
(instance? TelemereLoggerFactory lf))) (instance? TelemereLoggerFactory lf)))
(impl/add-intake-check! :tools-logging ;;;;
(fn []
(let [sending? (tools-logging->telemere?)
receiving?
(and sending?
(impl/test-intake! "`clojure.tools.logging` -> Telemere"
#(clojure.tools.logging/info %)))]
{:present? true (defn check-intake
:sending->telemere? sending? "Returns {:keys [present? sending->telemere? telemere-receiving?]}."
:telemere-receiving? receiving?}))) []
(let [sending? (tools-logging->telemere?)
receiving?
(and sending?
(impl/test-intake! "`clojure.tools.logging` -> Telemere"
#(clojure.tools.logging/info %)))]
{:present? true
:sending->telemere? sending?
:telemere-receiving? receiving?}))
(impl/add-intake-check! :tools-logging check-intake)
(impl/on-init
(when (enc/get-env {:as :bool} :clojure.tools.logging-to-telemere?)
(tools-logging->telemere!)))

View file

@ -8,10 +8,12 @@
: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.utils :as utils] [taoensso.telemere.utils :as utils]
[taoensso.telemere.timbre :as timbre] [taoensso.telemere.timbre :as timbre]
#?(:clj [taoensso.telemere.slf4j :as slf4j]) #_[taoensso.telemere.tools-logging :as tools-logging]
#?(:clj [clojure.tools.logging :as ctl]) #_[taoensso.telemere.streams :as streams]
#?(:clj [taoensso.telemere.slf4j :as slf4j])
#?(:clj [clojure.tools.logging :as ctl])
#?(:default [taoensso.telemere.handlers.console :as handlers:console]) #?(:default [taoensso.telemere.handlers.console :as handlers:console])
#?(:clj [taoensso.telemere.handlers.file :as handlers:file]) #?(:clj [taoensso.telemere.handlers.file :as handlers:file])
@ -577,9 +579,9 @@
(is (sm? (with-sig (-> (.atInfo sl) (.addKeyValue "k1" "v1") (.addKeyValue "k2" "v2") (.log))) {:data {:slf4j/kvs {"k1" "v1", "k2" "v2"}}}) "Fluent API: kvs") (is (sm? (with-sig (-> (.atInfo sl) (.addKeyValue "k1" "v1") (.addKeyValue "k2" "v2") (.log))) {:data {:slf4j/kvs {"k1" "v1", "k2" "v2"}}}) "Fluent API: kvs")
(testing "Markers" (testing "Markers"
(let [m1 (slf4j/est-marker! "M1") (let [m1 (#'slf4j/est-marker! "M1")
m2 (slf4j/est-marker! "M2") m2 (#'slf4j/est-marker! "M2")
cm (slf4j/est-marker! "Compound" "M1" "M2")] cm (#'slf4j/est-marker! "Compound" "M1" "M2")]
[(is (sm? (with-sig (.info sl cm "Hello")) {:data #:slf4j{:marker-names #{"Compound" "M1" "M2"}}}) "Legacy API: markers") [(is (sm? (with-sig (.info sl cm "Hello")) {:data #:slf4j{:marker-names #{"Compound" "M1" "M2"}}}) "Legacy API: markers")
(is (sm? (with-sig (-> (.atInfo sl) (.addMarker m1) (.addMarker cm) (.log))) {:data #:slf4j{:marker-names #{"Compound" "M1" "M2"}}}) "Fluent API: markers")])) (is (sm? (with-sig (-> (.atInfo sl) (.addMarker m1) (.addMarker cm) (.log))) {:data #:slf4j{:marker-names #{"Compound" "M1" "M2"}}}) "Fluent API: markers")]))
@ -813,26 +815,26 @@
#?(:clj #?(:clj
(deftest _open-telemetry (deftest _open-telemetry
[(testing "attr-name" [(testing "attr-name"
[(is (= (handlers:otel/attr-name :foo) "foo")) [(is (= (#'handlers:otel/attr-name :foo) "foo"))
(is (= (handlers:otel/attr-name :foo-bar-baz) "foo_bar_baz")) (is (= (#'handlers:otel/attr-name :foo-bar-baz) "foo_bar_baz"))
(is (= (handlers:otel/attr-name :foo/bar-baz) "foo.bar_baz")) (is (= (#'handlers:otel/attr-name :foo/bar-baz) "foo.bar_baz"))
(is (= (handlers:otel/attr-name :Foo/Bar-BAZ) "foo.bar_baz")) (is (= (#'handlers:otel/attr-name :Foo/Bar-BAZ) "foo.bar_baz"))
(is (= (handlers:otel/attr-name "Foo Bar-Baz") "foo_bar_baz")) (is (= (#'handlers:otel/attr-name "Foo Bar-Baz") "foo_bar_baz"))
(is (= (handlers:otel/attr-name :x1.x2/x3-x4 :foo/bar-baz) (is (= (#'handlers:otel/attr-name :x1.x2/x3-x4 :foo/bar-baz)
"x1.x2.x3_x4.foo.bar_baz"))]) "x1.x2.x3_x4.foo.bar_baz"))])
(testing "merge-prefix-map" (testing "merge-prefix-map"
[(is (= (handlers:otel/merge-prefix-map nil "pf" nil) nil)) [(is (= (#'handlers:otel/merge-prefix-map nil "pf" nil) nil))
(is (= (handlers:otel/merge-prefix-map nil "pf" {}) nil)) (is (= (#'handlers:otel/merge-prefix-map nil "pf" {}) nil))
(is (= (handlers:otel/merge-prefix-map {"a" "A"} "pf" {:a :A}) {"a" "A", "pf.a" :A})) (is (= (#'handlers:otel/merge-prefix-map {"a" "A"} "pf" {:a :A}) {"a" "A", "pf.a" :A}))
(is (= (handlers:otel/merge-prefix-map {} "pf" (is (= (#'handlers:otel/merge-prefix-map {} "pf"
{:a/b1 "v1" :a/b2 "v2" :nil nil, :map {:k1 "v1"}}) {:a/b1 "v1" :a/b2 "v2" :nil nil, :map {:k1 "v1"}})
{"pf.a.b1" "v1", "pf.a.b2" "v2", "pf.nil" nil, "pf.map" {:k1 "v1"}}))]) {"pf.a.b1" "v1", "pf.a.b2" "v2", "pf.nil" nil, "pf.map" {:k1 "v1"}}))])
(testing "as-attrs" (testing "as-attrs"
(is (= (str (is (= (str
(handlers:otel/as-attrs (#'handlers:otel/as-attrs
{:string "s", :keyword :foo/bar, :long 5, :double 5.0, :nil nil, {:string "s", :keyword :foo/bar, :long 5, :double 5.0, :nil nil,
:longs [5 5.0 5.0], :longs [5 5.0 5.0],
:doubles [5.0 5 5], :doubles [5.0 5 5],
@ -844,7 +846,7 @@
"{bools=[true, false, false], double=5.0, doubles=[5.0, 5.0, 5.0], keyword=\":foo/bar\", long=5, longs=[5, 5, 5], map=[[:k1 \"v1\"]], mixed=[5, \"5\", nil], nil=\"nil\", string=\"s\", strings=[\"a\", \"b\", \"c\"]}"))) "{bools=[true, false, false], double=5.0, doubles=[5.0, 5.0, 5.0], keyword=\":foo/bar\", long=5, longs=[5, 5, 5], map=[[:k1 \"v1\"]], mixed=[5, \"5\", nil], nil=\"nil\", string=\"s\", strings=[\"a\", \"b\", \"c\"]}")))
(testing "signal->attrs-map" (testing "signal->attrs-map"
(let [attrs-map handlers:otel/signal->attrs-map] (let [attrs-map #'handlers:otel/signal->attrs-map]
[(is (= (attrs-map nil { }) {"error" false})) [(is (= (attrs-map nil { }) {"error" false}))
(is (= (attrs-map :attrs {:attrs {:a1 :A1}}) {"error" false, :a1 :A1})) (is (= (attrs-map :attrs {:attrs {:a1 :A1}}) {"error" false, :a1 :A1}))
(is (is

View file

@ -95,21 +95,21 @@ See section [3-Config](./3-Config) for customization.
> Signal handlers process created signals to *do something with them* (analyse them, write them to console/file/queue/db, etc.) > Signal handlers process created signals to *do something with them* (analyse them, write them to console/file/queue/db, etc.)
| Platform | Condition | Handler | | Platform | Condition | Handler |
| -------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | -------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Clj | Always | [Console handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) that prints signals to `*out*` or `*err*`. | | Clj | Always | [Console handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) that prints signals to `*out*` or `*err*`. |
| Cljs | Always | [Console handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) that prints signals to the **browser console**. | | Cljs | Always | [Console handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) that prints signals to the **browser console**. |
| Clj | [OpenTelemetry API](https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-api) present | [OpenTelemetry handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:open-telemetry-logger) that emits signals as log records to a configured [`LoggerProvider`](https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider). | | Clj      | [OpenTelemetry API](https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-api) present | [OpenTelemetry handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:open-telemetry-logger) that emits signals as log records to a configured [`LoggerProvider`](https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider). |
**Default signal intakes**: **Default signal intakes**:
> Telemere can create signals from relevant **external API calls**, etc. > Telemere can create signals from relevant **external API calls**, etc.
| Platform | Condition | Signals from | | Platform | Condition | Signals from |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Clj | [SLF4J API](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) and [Telemere SLF4J backend](https://clojars.org/com.taoensso/slf4j-telemere) present | [SLF4J](https://www.slf4j.org/) logging calls. | | Clj | [SLF4J API](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) and [Telemere SLF4J backend](https://clojars.org/com.taoensso/slf4j-telemere) present | [SLF4J](https://www.slf4j.org/) logging calls. |
| Clj | [clojure.tools.logging](https://mvnrepository.com/artifact/org.clojure/tools.logging) present and [`tools-logging->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#tools-logging-%3Etelemere!) called | [clojure.tools.logging](https://github.com/clojure/tools.logging) logging calls. | | Clj | [clojure.tools.logging](https://mvnrepository.com/artifact/org.clojure/tools.logging) present and [`tools-logging->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.tools-logging#tools-logging-%3Etelemere!) called | [clojure.tools.logging](https://github.com/clojure/tools.logging) logging calls. |
| Clj | [`streams->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#streams-%3Etelemere!) called | Output to `System/out` and `System/err` streams. | | Clj | [`streams->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#streams-%3Etelemere!) called | Output to `System/out` and `System/err` streams. |
Run [`check-intakes`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-intakes) to help verify/debug: Run [`check-intakes`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-intakes) to help verify/debug:

View file

@ -20,12 +20,12 @@ This flow is described by [`help:signal-flow`](https://cljdoc.org/d/com.taoensso
For more info see: For more info see:
| Var | Help with | | Var | Help with |
| :-- | :-- | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------- |
| [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) | List of signal creators | [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) | List of signal creators |
| [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Options for signal creators | [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Options for signal creators |
| [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) | Signal map content | [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) | Signal map content |
| [`help:signal-flow`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-flow) | Ordered flow from signal creation to handling | [`help:signal-flow`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-flow) | Ordered flow from signal creation to handling |
| [`help:signal-filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-filters) | API for configuring signal filters | [`help:signal-filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-filters) | API for configuring signal filters |
| [`help:signal-handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-handlers) | API for configuring signal handlers | [`help:signal-handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-handlers) | API for configuring signal handlers |
| [`help:signal-formatters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-formatters) | Signal formatters for use by handlers | [`help:signal-formatters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-formatters) | Signal formatters for use by handlers |

View file

@ -29,9 +29,7 @@ See section [4-Handlers](./4-Handlers).
To do this: To do this:
1. Ensure that you have the `clojure.tools.logging` dependency, and 1. Ensure that you have the `clojure.tools.logging` dependency, and
2. Call [`tools-logging->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#tools-logging-%3Etelemere!), or set the relevant system config as described in its docstring. 2. Call [`tools-logging->telemere!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.tools-logging#tools-logging-%3Etelemere!), or set the relevant system config as described in its docstring.
Note that the `tools-logging->telemere!` var will be present **only if** the `clojure.tools.logging` dependency is present.
Verify successful intake with [`check-intakes`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-intakes): Verify successful intake with [`check-intakes`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-intakes):
@ -90,9 +88,7 @@ Telemere can send signals as [`LogRecords`](https://opentelemetry.io/docs/specs/
To do this: To do this:
1. Ensure that you have the [OpenTelemetry Java](https://github.com/open-telemetry/opentelemetry-java) dependency. 1. Ensure that you have the [OpenTelemetry Java](https://github.com/open-telemetry/opentelemetry-java) dependency.
2. Use [`handler:open-telemetry-logger`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:open-telemetry-logger) to create an appropriately configured handler, and register it with [`add-handler!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#add-handler!). 2. Use [`handler:open-telemetry-logger`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry-logger) to create an appropriately configured handler, and register it with [`add-handler!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#add-handler!).
Note that the `handler:open-telemetry-logger` var will be present **only if** the OpenTelemetry Java dependency is present.
## Tufte ## Tufte

View file

@ -4,13 +4,13 @@ Signal handlers process created signals to *do something with them* (analyse the
The following handlers are included out-the-box: The following handlers are included out-the-box:
| Name | Platform | Writes signals to | Writes signals as | | Name | Platform | Writes signals to | Writes signals as |
| :---------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- | | :------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- |
| [`handler:console`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | Clj | `*out*` or `*err*` | String ([edn](https://github.com/edn-format/edn), JSON, formatted, etc.) | | [`handler:console`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | Clj | `*out*` or `*err*` | String ([edn](https://github.com/edn-format/edn), JSON, formatted, etc.) |
| [`handler:console`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | Cljs | Browser console | String ([edn](https://github.com/edn-format/edn), JSON, formatted, etc.) | | [`handler:console`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | Cljs | Browser console | String ([edn](https://github.com/edn-format/edn), JSON, formatted, etc.) |
| [`handler:console-raw`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) | Cljs | Browser console | Raw data (for [cljs-devtools](https://github.com/binaryage/cljs-devtools), etc.) | | [`handler:console-raw`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) | Cljs | Browser console | Raw data (for [cljs-devtools](https://github.com/binaryage/cljs-devtools), etc.) |
| [`handler:file`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:file) | Clj | File/s on disk | String ([edn](https://github.com/edn-format/edn), JSON, formatted, etc.) | | [`handler:file`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:file) | Clj | File/s on disk | String ([edn](https://github.com/edn-format/edn), JSON, formatted, etc.) |
| [`handler:open-telemetry-logger`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:open-telemetry-logger) | Clj | [OpenTelemetry](https://opentelemetry.io/) [Java client](https://github.com/open-telemetry/opentelemetry-java) | [LogRecord](https://opentelemetry.io/docs/specs/otel/logs/data-model/) | | [`handler:open-telemetry-logger`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry-logger) | Clj | [OpenTelemetry](https://opentelemetry.io/) [Java client](https://github.com/open-telemetry/opentelemetry-java) | [LogRecord](https://opentelemetry.io/docs/specs/otel/logs/data-model/) |
- See relevant docstrings (links above) for more info. - See relevant docstrings (links above) for more info.
- See section [8-Community](8-Community.md) for additional handlers. - See section [8-Community](8-Community.md) for additional handlers.