Skip to content

Commit

Permalink
[fix] Fix broken AOT support, add AOT tests
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ptaoussanis committed Apr 24, 2024
1 parent b98e492 commit ffea1a3
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 155 deletions.
2 changes: 2 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
[[org.clojure/clojure "1.11.2"]
[com.github.clj-easy/graal-build-time "1.0.5"]]}

:test {:aot [taoensso.telemere-tests]}
:dev
{:jvm-opts
["-server"
"-Dtaoensso.elide-deprecated=true"
"-Dtaoensso.telemere.auto-handlers=false"
"-Dclojure.tools.logging-to-telemere?=true"]

:global-vars
Expand Down
58 changes: 37 additions & 21 deletions slf4j/src/taoensso/telemere/slf4j.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(ns ^:no-doc taoensso.telemere.slf4j
"Private ns, implementation detail.
Intake support: SLF4J -> Telemere.
(ns taoensso.telemere.slf4j
"Intake support for SLF4J -> Telemere.
Telemere will attempt to load this ns automatically when possible.
To use Telemere as your SLF4J backend/provider, just include the
`com.taoensso/slf4j-telemere` dependency on your classpath.
Expand All @@ -18,7 +18,11 @@

(:require
[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

Expand All @@ -40,10 +44,10 @@

(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))

(defn est-marker!
(defn- est-marker!
"Private util for tests, etc.
Globally establishes (compound) `org.slf4j.Marker` with name `n` and mutates it
(all occurences!) to have exactly the given references. Returns the (compound) marker."
Expand All @@ -55,7 +59,7 @@

(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."
;; We use `BasicMarkerFactory` so:
;; 1. Our markers are just labels (no other content besides their name).
Expand Down Expand Up @@ -95,7 +99,7 @@

;;;; Intake fns (called by `TelemereLogger`)

(defn allowed?
(defn- allowed?
"Private, don't use.
Called by `com.taoensso.telemere.slf4j.TelemereLogger`."
[^org.slf4j.event.Level level]
Expand Down Expand Up @@ -134,7 +138,7 @@
:slf4j/kvs kvs)})
nil)

(defn log!
(defn- log!
"Private, don't use.
Called by `com.taoensso.telemere.slf4j.TelemereLogger`."

Expand Down Expand Up @@ -172,15 +176,27 @@
(org.slf4j.MDC/getCopyOfContextMap)
(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
:sending->telemere? sending?
:telemere-receiving? receiving?})))
;;;;

(defn check-intake
"Returns {:keys [present? sending->telemere? telemere-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"}))
45 changes: 10 additions & 35 deletions src/taoensso/telemere.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -375,25 +375,6 @@
streams/streams->telemere!
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))

;;;; Handlers
Expand All @@ -403,20 +384,6 @@
#?(:cljs handlers:console/handler:console-raw)
#?(: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

(comment
Expand Down Expand Up @@ -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
(with-handler :hid1 (handlers/console-handler) {} (log! "Message"))

Expand All @@ -464,5 +441,3 @@
(do (let [hf (handlers/file-handler)] (hf sig) (hf)))
(do (let [hf (handlers/console-handler)] (hf sig) (hf)))
#?(:cljs (let [hf (handlers/raw-console-handler)] (hf sig) (hf)))))

;;;;
31 changes: 20 additions & 11 deletions src/taoensso/telemere/handlers/open_telemetry.clj
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
(ns ^:no-doc taoensso.telemere.handlers.open-telemetry
"Private ns, implementation detail.
Core OpenTelemetry handlers.
(ns taoensso.telemere.handlers.open-telemetry
"Core OpenTelemetry handler and utils.
Telemere will attempt to load this ns automatically when possible.
Needs `OpenTelemetry Java`,
Ref. <https://github.com/open-telemetry/opentelemetry-java>."

(:require
[clojure.string :as str]
[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
[io.opentelemetry.api.logs LoggerProvider Severity]
Expand All @@ -21,7 +23,7 @@

;;;; Implementation

(defn level->severity
(defn- level->severity
^Severity [level]
(case level
:trace Severity/TRACE
Expand All @@ -33,7 +35,7 @@
:report Severity/INFO4
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.
Ref. <https://opentelemetry.io/docs/specs/semconv/general/attribute-naming/>."
(enc/fmemoize
Expand All @@ -49,7 +51,7 @@
(comment (enc/qb 1e6 (attr-name :x1.x2/x3-x4 :Foo/Bar-BAZ))) ; 63.6

;; AttributeTypes: String, Long, Double, Boolean, and arrays
(defprotocol IAttr+ (attr+ [_aval akey builder]))
(defprotocol IAttr+ (^:private attr+ [_aval akey builder]))
(extend-protocol IAttr+
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))
Expand Down Expand Up @@ -78,7 +80,7 @@

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."
^Attributes [m]
(if (empty? m)
Expand All @@ -89,7 +91,7 @@

(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`."
[to prefix from]
(enc/cond
Expand All @@ -103,7 +105,7 @@

(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,
Ref. <https://opentelemetry.io/docs/specs/otel/logs/data-model/>."
[attrs-key signal]
Expand Down Expand Up @@ -181,7 +183,7 @@

;;;; Handler

(defn ^:public handler:open-telemetry-logger
(defn handler:open-telemetry-logger
"Experimental, subject to change!! Feedback very welcome!
Returns a (fn handler [signal]) that:
Expand Down Expand Up @@ -216,3 +218,10 @@
(.setSeverity severity)
(.setBody msg)
(.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))))
10 changes: 10 additions & 0 deletions src/taoensso/telemere/impl.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,18 @@

;;;; 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 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

#?(:clj
Expand Down
54 changes: 33 additions & 21 deletions src/taoensso/telemere/streams.clj
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
(ns ^:no-doc taoensso.telemere.streams
"Private ns, implementation detail.
Intake support: standard stream/s -> Telemere."
(ns taoensso.telemere.streams
"Intake support for standard stream/s -> Telemere."
(:refer-clojure :exclude [binding])
(:require
[taoensso.encore :as enc :refer [binding have have?]]
[taoensso.telemere.impl :as impl]))

(enc/defonce orig-*out* "Original `*out*` on ns load" *out*)
(enc/defonce orig-*err* "Original `*err*` on ns load" *err*)
(enc/defonce ^: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 ^:private orig-*out* "Original `*out*` on ns load" *out*)
(enc/defonce ^:private orig-*err* "Original `*err*` on ns load" *err*)
(enc/defonce ^:no-doc ^:dynamic prev-*out* "Previous `*out*` (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-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
"Returns a `java.io.PrintStream` that will flush to Telemere signals with given opts."
(defn ^:no-doc telemere-print-stream
"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]}]
(let [baos
(proxy [java.io.ByteArrayOutputStream] []
Expand All @@ -42,6 +45,8 @@
(java.io.PrintStream. baos true ; Auto flush
java.nio.charset.StandardCharsets/UTF_8)))

;;;;

(defmacro ^:public with-out->telemere
"Executes form with `*out*` bound to flush to Telemere signals with given opts."
([ form] `(with-out->telemere nil ~form))
Expand Down Expand Up @@ -143,14 +148,21 @@
(streams->telemere! {})
(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
(fn []
(let [sending? (boolean @orig-err_)
receiving? (and sending? (impl/test-intake! "`System/err` -> Telemere" #(.println System/err %)))]
{:sending->telemere? sending?, :telemere-receiving? receiving?})))
;;;;

(defn check-out-intake
"Returns {:keys [sending->telemere? telemere-receiving?]}."
[]
(let [sending? (boolean @orig-out_)
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)
Loading

0 comments on commit ffea1a3

Please sign in to comment.