Skip to content

Commit

Permalink
OpenTelemetry Protocol (OTLP) appender
Browse files Browse the repository at this point in the history
Requires io.opentelemetry/opentelemetry-api.

With Java Agent:

Activate an appender configured by the OpenTelemetry Java Agent:
```clj
(let [logger-provider (.getLogsBridge (GlobalOpenTelemetry/get))
      appender (taoensso.timbre.appenders.community.otlp/otlp-appender logger-provider)]
  (taoensso.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
* open-telemetry/opentelemetry-java-instrumentation#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 (taoensso.timbre.appenders.community.otlp/otlp-appender logger-provider)]
  (taoensso.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)))
```

I took inspiration from `taoensso.timbre.appenders.community.sentry`.

Once steffan-westcott/clj-otel#8 is implemented,
the actual log emission should be replaced with using clj-otel's API.
  • Loading branch information
devurandom committed Jun 19, 2023
1 parent 4b421a3 commit a78ddcd
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 1 deletion.
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -52,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
106 changes: 106 additions & 0 deletions src/taoensso/timbre/appenders/community/otlp.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
(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 (taoensso.timbre.appenders.community.otlp/otlp-appender logger-provider)]
(taoensso.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 (taoensso.timbre.appenders.community.otlp/otlp-appender logger-provider)]
(taoensso.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])
(: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)))

(defn otlp-appender
[^LoggerProvider logger-provider]
{:enabled? true
:async? true
:min-level nil
:rate-limit nil
:output-fn :inherit
:fn
(fn [data]
(let [{:keys [^Date instant level ^String ?ns-str ?file ?line ?err vargs msg_ context]} data
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 (cond-> context
(and ?file (not (contains? context :file)))
(assoc :file ?file)
(and ?line (not (contains? context :line)))
(assoc :line ?line)
(and ?ex-data (not (contains? context :ex-data)))
(assoc :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)))))})

0 comments on commit a78ddcd

Please sign in to comment.