Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenTelemetry Protocol (OTLP) appender #374

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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].

<!--- Standard links -->
[Taoensso.com]: https://www.taoensso.com
Expand All @@ -265,3 +268,4 @@ Copyright &copy; 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
11 changes: 6 additions & 5 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
:author "Peter Taoussanis <https://www.taoensso.com>"
: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
Expand Down Expand Up @@ -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"]
Expand Down
83 changes: 56 additions & 27 deletions src/taoensso/timbre.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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) _ "<pr-str failed>"))))))))))

(defn- default-arg->str-fn [x]
(enc/cond
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
113 changes: 113 additions & 0 deletions src/taoensso/timbre/appenders/community/otlp.clj
Original file line number Diff line number Diff line change
@@ -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)))))})
18 changes: 7 additions & 11 deletions src/taoensso/timbre/appenders/example.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down