diff --git a/README.md b/README.md index 97aff8a0..d185337e 100644 --- a/README.md +++ b/README.md @@ -207,12 +207,15 @@ See also `car-appender/query-entries`. ### Community appenders -A number of community appenders are included out-the-box [here](https://github.com/ptaoussanis/timbre/tree/master/src/taoensso/timbre/appenders/community). These include appenders for Android, Logstash, Slack, Sentry, NodeJS, Syslog, PostgreSQL, etc. +A number of [community appenders][] are included with Timbre. -Thanks goes to the respective authors! -**Please see the relevant namespace docstring for details**. +Thanks to the relevant authors! Please see **appender namespace docstrings** for details. -GitHub PRs for new appenders and for appender maintenance very welcome! +GitHub PRs very welcome for: + + - Maintenance of any existing [community appenders][] (thank you!!). + - Additional **dependency-free** appenders. (See [example template](https://github.com/ptaoussanis/timbre/blob/master/src/taoensso/timbre/appenders/example.clj)). + - Additional links to **externally-hosted** appenders in the table below. ## More community tools, appenders, etc. @@ -239,7 +242,7 @@ Otherwise, you can reach me at [Taoensso.com][]. Happy hacking! ## License Distributed under the [EPL v1.0][] \(same as Clojure). -Copyright © 2015-2022 [Peter Taoussanis][Taoensso.com]. +Copyright © 2015-2023 [Peter Taoussanis][Taoensso.com]. [Taoensso.com]: https://www.taoensso.com @@ -265,3 +268,4 @@ Copyright © 2015-2022 [Peter Taoussanis][Taoensso.com]. [ClojureWerkz]: http://clojurewerkz.org/ [config API]: http://ptaoussanis.github.io/timbre/taoensso.timbre.html#var-*config* [default config]: http://ptaoussanis.github.io/timbre/taoensso.timbre.html#var-default-config +[community appenders]: https://github.com/ptaoussanis/timbre/tree/master/src/taoensso/timbre/appenders/community \ No newline at end of file diff --git a/project.clj b/project.clj index 64141291..56f31be7 100644 --- a/project.clj +++ b/project.clj @@ -2,11 +2,12 @@ :author "Peter Taoussanis " :description "Pure Clojure/Script logging library" :url "https://github.com/ptaoussanis/timbre" - :license {:name "Eclipse Public License" - :url "http://www.eclipse.org/legal/epl-v10.html" - :distribution :repo - :comments "Same as Clojure"} :min-lein-version "2.3.3" + + :license + {:name "Eclipse Public License 1.0" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :global-vars {*warn-on-reflection* true *assert* true @@ -51,7 +52,7 @@ [server-socket "1.0.0"] [org.zeromq/cljzmq "0.1.4"] [cljs-node-io "1.1.2"] ; Node spit appender - ]} + [com.github.steffan-westcott/clj-otel-api "0.2.3"]]} :extra {:source-paths [ "src" "extra/src"] diff --git a/src/taoensso/timbre.cljc b/src/taoensso/timbre.cljc index 36ff46ea..3b09d1ab 100644 --- a/src/taoensso/timbre.cljc +++ b/src/taoensso/timbre.cljc @@ -159,38 +159,55 @@ ;;;; Compile-time filtering +#?(:clj (defonce ^:private compile-time-config_ (atom {}))) #?(:clj - (def ^:private compile-time-min-level - (when-let [level - (or - (enc/read-sys-val "taoensso.timbre.min-level.edn" "TAOENSSO_TIMBRE_MIN_LEVEL_EDN") - (enc/read-sys-val "TIMBRE_LEVEL") ; Legacy - (enc/read-sys-val "TIMBRE_LOG_LEVEL") ; Legacy - )] - - (let [level (if (string? level) (keyword level) level)] ; Legacy - (valid-level level) - (println (str "Compile-time (elision) Timbre min-level: " level)) + (defonce ^:private compile-time-min-level + (let [level + (or + (enc/read-sys-val "taoensso.timbre.min-level.edn" "TAOENSSO_TIMBRE_MIN_LEVEL_EDN") + (enc/read-sys-val "TIMBRE_LEVEL") ; Legacy + (enc/read-sys-val "TIMBRE_LOG_LEVEL") ; Legacy + ) + + level (if (string? level) (keyword level) level) ; Legacy + present? (some? level) + valid? (valid-level? level)] + + (swap! compile-time-config_ assoc :min-level + (when present? + (if valid? + level + [:timbre/invalid-min-level + {:value level :type (type level)}]))) + + (when valid? + ;; (println (str "Compile-time (elision) Timbre min-level: " level)) level)))) #?(:clj - (def ^:private compile-time-ns-filter + (defonce ^:private compile-time-ns-filter (let [ns-pattern (or (enc/read-sys-val "taoensso.timbre.ns-pattern.edn" "TAOENSSO_TIMBRE_NS_PATTERN_EDN") (enc/read-sys-val "TIMBRE_NS_PATTERN") ; Legacy (legacy-ns-filter ; Legacy (enc/read-sys-val "TIMBRE_NS_WHITELIST") - (enc/read-sys-val "TIMBRE_NS_BLACKLIST")))] + (enc/read-sys-val "TIMBRE_NS_BLACKLIST"))) + + ns-pattern ; Support legacy :whitelist, :blacklist + (if (map? ns-pattern) + {:allow (or (:allow ns-pattern) (:whitelist ns-pattern)) + :deny (or (:deny ns-pattern) (:blacklist ns-pattern))} + ns-pattern) + + present? (some? ns-pattern) + ns-pattern (or ns-pattern "*")] - (let [ns-pattern ; Legacy - (if (map? ns-pattern) - {:allow (or (:allow ns-pattern) (:whitelist ns-pattern)) - :deny (or (:deny ns-pattern) (:blacklist ns-pattern))} - ns-pattern)] + (swap! compile-time-config_ assoc :ns-pattern ns-pattern) + ;; (when present? + ;; (println (str "Compile-time (elision) Timbre ns-pattern: " ns-pattern))) - (when ns-pattern (println (str "Compile-time (elision) Timbre ns-pattern: " ns-pattern))) - (or ns-pattern "*"))))) + ns-pattern))) #?(:clj (defn -elide? @@ -412,8 +429,13 @@ (when-let [err ?err] (when-let [ef (get output-opts :error-fn default-output-error-fn)] (when-not (get output-opts :no-stacktrace?) ; Back compatibility - (str enc/system-newline - (ef data))))))))) + (enc/catching + (str enc/system-newline (ef data)) _ + (str + enc/system-newline + "[TIMBRE WARNING]: `error-fn` failed, falling back to `pr-str`:" + enc/system-newline + (enc/catching (pr-str err) _ "")))))))))) (defn- default-arg->str-fn [x] (enc/cond @@ -1084,9 +1106,7 @@ - `with-config`, `with-merged-config` ; Bind *config* - `with-min-level` ; Bind *config* :min-level - MAIN CONFIG OPTIONS - :min-level Logging will occur only if a logging call's level is >= this min-level. Possible values, in order: @@ -1203,7 +1223,14 @@ - `TAOENSSO_TIMBRE_MIN_LEVEL_EDN` env var (read as EDN) - `TAOENSSO_TIMBRE_NS_PATTERN_EDN` env var (read as EDN) - Note that compile-time options will OVERRIDE options in `*config*`." + Note that compile-time options will OVERRIDE options in `*config*`. + + DEBUGGING INITIAL CONFIG + See `:_init-config` for information re: Timbre's config on initial load. + These keys are set only once on initial load, and changing them will + have no effect: + :loaded-from-source ; e/o #{:default :prop :res :res-env} + :compile-time-config ; {:keys [min-level ns-filter]} for compile-time elision" #?(:cljs default-config :clj @@ -1214,8 +1241,10 @@ :res "taoensso.timbre.config.edn" :res-env "taoensso.timbre.config-resource"})] - (println (str "Loading initial Timbre config from: " source)) - config))) + ;; (println (str "Loading initial Timbre config from: " source)) + (assoc config :_init-config + {:loaded-from-source source + :compile-time-config @compile-time-config_})))) ;;;; Deprecated diff --git a/src/taoensso/timbre/appenders/community/otlp.clj b/src/taoensso/timbre/appenders/community/otlp.clj new file mode 100644 index 00000000..d22bbdb7 --- /dev/null +++ b/src/taoensso/timbre/appenders/community/otlp.clj @@ -0,0 +1,113 @@ +(ns taoensso.timbre.appenders.community.otlp + "OpenTelemetry Protocol (OTLP) appender. + Requires com.github.steffan-westcott/clj-otel-api. + + # With Java Agent + + Activate an appender configured by the OpenTelemetry Java Agent: + ```clj + (let [logger-provider (.getLogsBridge (GlobalOpenTelemetry/get)) + appender (otlp/appender logger-provider)] + (timbre/merge-config! {:appenders {:otlp appender}})) + ``` + + Note: When relying on the OpenTelemetry Java Agent 1.x, you need + to explicitly enable the logs exporter with `OTEL_LOGS_EXPORTER=otlp`. + This will become the default with the release of Java Agent 2.0, cf. + * https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md#version-1270-2023-06-14 + * https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8647 + + # Without Java Agent + + If you want autoconfiguration without the Java Agent, you also need + io.opentelemetry/opentelemetry-sdk-extension-autoconfigure and + io.opentelemetry/opentelemetry-exporter-otlp on the classpath. + + Create an autoconfigured appender and activate it: + ```clj + (let [logger-provider (.getSdkLoggerProvider + (.getOpenTelemetrySdk + (.build + (AutoConfiguredOpenTelemetrySdk/builder)))) + appender (otlp/appender logger-provider)] + (timbre/merge-config! {:appenders {:otlp appender}})) + ``` + + If you already have an instance of `GlobalOpenTelemetry`, e.g. created + by the OpenTelemetry Java Agent, you need to prevent setting the newly + created SDK as the global default: + ```clj + (.build + (doto (AutoConfiguredOpenTelemetrySdk/builder) + (.setResultAsGlobal false))) + ```" + {:author "Dennis Schridde (@devurandom)"} + (:require + [steffan-westcott.clj-otel.api.attributes :as attr] + [taoensso.encore :as enc]) + (:import + (io.opentelemetry.api.logs LoggerProvider Severity) + (java.util Date))) + +(set! *warn-on-reflection* true) + +(def ^:private default-severity + Severity/INFO) + +(def ^:private timbre->otlp-levels + {:trace Severity/TRACE + :debug Severity/DEBUG + :info Severity/INFO + :warn Severity/WARN + :error Severity/ERROR + :fatal Severity/FATAL + :report default-severity}) + +(defn- single-map [xs] + (let [[x & r] xs] + (when (and (map? x) (not r)) + x))) + +; TODO: taoensso.encore seems to be missing this: +(defn- assoc-some-nx + ([m k v] (if (contains? m k) m (enc/assoc-some m k v))) + ([m k v & kvs] (enc/reduce-kvs assoc-some-nx (enc/assoc-some m k v) kvs)) + ([m kvs] + (reduce-kv + (fn [m k v] (if (contains? m k) m (enc/assoc-some m k v))) + (if (nil? m) {} m) + kvs))) + +(defn appender + [^LoggerProvider logger-provider] + {:enabled? true + :async? true + :min-level nil + :rate-limit nil + :output-fn :inherit + :fn + (fn [{:keys [^Date instant level ^String ?ns-str ?file ?line ?err vargs msg_ context]}] + (let [actual-instant (.toInstant instant) + severity (get timbre->otlp-levels level default-severity) + arg (single-map vargs) + message (if-let [msg (:msg arg)] + msg + (force msg_)) + ?ex-data (ex-data ?err) + extra (assoc-some-nx context + :file ?file + :line ?line + :ex-data ?ex-data) + event (merge (dissoc arg :msg) + extra) + attributes (attr/->attributes event) + ; TODO: Use clj-otel once it supports the logs API. + ; cf. https://github.com/steffan-westcott/clj-otel/issues/8 + logger (.get logger-provider ?ns-str)] + (.emit + (doto (.logRecordBuilder logger) + (.setTimestamp actual-instant) + (.setSeverity severity) + (.setSeverityText (.toString severity)) + (.setAllAttributes attributes) + (.setBody message)))))}) diff --git a/src/taoensso/timbre/appenders/example.clj b/src/taoensso/timbre/appenders/example.clj index d9712a9a..2beeac05 100644 --- a/src/taoensso/timbre/appenders/example.clj +++ b/src/taoensso/timbre/appenders/example.clj @@ -2,32 +2,28 @@ "You can copy this namespace if you'd like a starting template for writing your own Timbre appender. - PRs for new community appenders welcome! + PRs for new *dependency-free* community appenders welcome! + + NB See the `timbre/*config*` docstring for up-to-date info + Timbre's appender API." - TODO Please document any dependency GitHub links here, e.g.: - Requires https://github.com/clojure/java.jdbc, - https://github.com/swaldman/c3p0" {:author "TODO Your Name (@your-github-username)"} (:require [taoensso.encore :as enc] [taoensso.timbre :as timbre])) -;; TODO If you add any special ns imports above, please remember to update -;; Timbre's `project.clj` to include the necessary dependencies under -;; the `:community` profile - ;; TODO Please mark any implementation vars as ^:private (defn example-appender ; Appender constructor "Docstring to explain any special opts to influence appender construction, etc. Returns the appender map. May close over relevant state, etc." - [{:as appender-opts :keys []}] ; Always take an opts map, even if unused + [{:as appender-opts :keys []}] ; TODO Always take an opts map, even if unused (let [shutdown?_ (atom false)] ; See :shutdown-fn below - ;; Return a new appender (just a map), see `timbre/example-config` - ;; for info on all available keys: + ;; Return a new appender (just a map), + ;; see `timbre/*config*` docstring for info on all available keys: {:enabled? true ; Enable new appenders by default ;; :async? true ; Use agent for appender dispatch? Useful for slow dispatch